source: vlo/branches/vlo-3.3/vlo-web-app/src/main/java/eu/clarin/cmdi/vlo/wicket/panels/record/HierarchyPanel.java @ 6679

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

removed obsolete todo

File size: 14.4 KB
Line 
1/*
2 * Copyright (C) 2015 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.panels.record;
18
19import com.google.common.base.Function;
20import com.google.common.collect.Iterators;
21import eu.clarin.cmdi.vlo.FacetConstants;
22import eu.clarin.cmdi.vlo.service.solr.SolrDocumentService;
23import eu.clarin.cmdi.vlo.wicket.components.AjaxFallbackLinkLabel;
24import eu.clarin.cmdi.vlo.wicket.components.IndicatingNestedTree;
25import eu.clarin.cmdi.vlo.wicket.components.NamedRecordPageLink;
26import eu.clarin.cmdi.vlo.wicket.model.SolrDocumentModel;
27import java.io.Serializable;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.Iterator;
31import java.util.Objects;
32import org.apache.solr.common.SolrDocument;
33import org.apache.wicket.Component;
34import org.apache.wicket.MarkupContainer;
35import org.apache.wicket.ajax.AjaxRequestTarget;
36import org.apache.wicket.behavior.AttributeAppender;
37import org.apache.wicket.behavior.Behavior;
38import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxFallbackLink;
39import org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree;
40import org.apache.wicket.extensions.markup.html.repeater.util.SortableTreeProvider;
41import org.apache.wicket.markup.html.WebMarkupContainer;
42import org.apache.wicket.markup.html.basic.Label;
43import org.apache.wicket.markup.html.link.Link;
44import org.apache.wicket.markup.html.panel.GenericPanel;
45import org.apache.wicket.markup.repeater.Item;
46import org.apache.wicket.markup.repeater.data.DataView;
47import org.apache.wicket.markup.repeater.data.IDataProvider;
48import org.apache.wicket.model.AbstractReadOnlyModel;
49import org.apache.wicket.model.IModel;
50import org.apache.wicket.model.Model;
51import org.apache.wicket.model.PropertyModel;
52import org.apache.wicket.spring.injection.annot.SpringBean;
53
54/**
55 *
56 * @author Twan Goosen <twan.goosen@mpi.nl>
57 */
58public class HierarchyPanel extends GenericPanel<SolrDocument> {
59
60    /**
61     * Number of parent nodes shown initially (before 'show all' expansion)
62     */
63    public static final int INITIAL_PARENTS_SHOWN = 5;
64    public static final int INITIAL_CHILRDEN_SHOWN = 50;
65
66    @SpringBean
67    private SolrDocumentService documentService;
68
69    private final IDataProvider<SolrDocument> parentsProvider;
70    private final MarkupContainer treeContainer;
71    private final AbstractTree<ModelWrapper<SolrDocument>> tree;
72    private final IModel<SolrDocument> pageDocumentModel;
73
74    public HierarchyPanel(String id, IModel<SolrDocument> documentModel) {
75        super(id, documentModel);
76        pageDocumentModel = documentModel;
77
78        parentsProvider = new ParentsDataProvider();
79
80        add(createParentLinks("parents"));
81
82        tree = createTree("tree");
83
84        treeContainer = new WebMarkupContainer("treeContainer");
85        treeContainer.setOutputMarkupId(true);
86        treeContainer.add(tree);
87        add(treeContainer);
88
89        setOutputMarkupId(true);
90    }
91
92    private Component createParentLinks(String id) {
93        // data view of (first N) parents and a link to load all in an
94        // Ajax-updatable container
95        final WebMarkupContainer container = new WebMarkupContainer(id);
96
97        // actual view
98        final DataView<SolrDocument> parentsView = new DataView<SolrDocument>("parentsList", parentsProvider, INITIAL_PARENTS_SHOWN) {
99
100            @Override
101            protected void populateItem(final Item<SolrDocument> item) {
102                item.add(new NamedRecordPageLink("link", item.getModel()));
103                item.add(new IndicatingAjaxFallbackLink("up") {
104
105                    @Override
106                    public void onClick(AjaxRequestTarget target) {
107                        // tree root up one level, expand root for traceability by user
108                        HierarchyPanel.this.setModel(item.getModel());
109                        tree.expand(new ModelWrapper<>(item.getModel()));
110                        if (target != null) {
111                            target.add(HierarchyPanel.this);
112                        }
113                    }
114                });
115
116                item.add(new Behavior() {
117
118                    @Override
119                    public void onConfigure(Component component) {
120                        // null parent, hide
121                        component.setVisible(item.getModelObject() != null);
122                    }
123
124                });
125            }
126
127            @Override
128            protected void onConfigure() {
129                // hide if there are no parents to show
130                setVisible(parentsProvider.size() > 0);
131            }
132        };
133
134        // ajax link to load the entire list, only shown if applicable
135        final Link showAllLink = new IndicatingAjaxFallbackLink("showAll") {
136
137            @Override
138            public void onClick(AjaxRequestTarget target) {
139                parentsView.setItemsPerPage(parentsProvider.size());
140                if (target != null) {
141                    target.add(container);
142                }
143            }
144
145            @Override
146            protected void onConfigure() {
147                // hide if there are no more parents to load
148                setVisible(parentsView.getItemCount() > parentsView.getItemsPerPage());
149            }
150        };
151        // show how many ndoes can be loaded
152        showAllLink.add(new Label("itemCount", new PropertyModel<Integer>(parentsProvider, "size")));
153
154        return container
155                .add(parentsView)
156                .add(showAllLink)
157                .setOutputMarkupId(true);
158    }
159
160    private AbstractTree createTree(String id) {
161        final HierarchyTreeProvider treeProvider = new HierarchyTreeProvider();
162        final AbstractTree<ModelWrapper<SolrDocument>> result = new IndicatingNestedTree<ModelWrapper<SolrDocument>>(id, treeProvider) {
163
164            @Override
165            protected Component newContentComponent(String id, final IModel<ModelWrapper<SolrDocument>> node) {
166                if (node.getObject().isLimit()) {
167                    return new AjaxFallbackLinkLabel(id, node, Model.of("Show all... (" + node.getObject().getCount() + ")")) {
168
169                        @Override
170                        public void onClick(AjaxRequestTarget target) {
171                            treeProvider.setChildrenShown(null);
172                            target.add(treeContainer);
173                        }
174
175                    };
176                } else {
177                    return new NamedRecordPageLink(id, node.getObject().getModel()) {
178
179                        @Override
180                        protected void onConfigure() {
181                            setEnabled(!node.equals(pageDocumentModel));
182                        }
183                    };
184                }
185            }
186
187        };
188        // style of tree depends on presence of parent nodes
189        result.add(new AttributeAppender("class", new AbstractReadOnlyModel<String>() {
190
191            @Override
192            public String getObject() {
193                if (parentsProvider.size() > 0) {
194                    return "has-parent";
195                } else {
196                    return null;
197                }
198            }
199        }, " "));
200        return result;
201    }
202
203    @Override
204    public void detachModels() {
205        super.detachModels();
206        pageDocumentModel.detach();
207    }
208
209    private class HierarchyTreeProvider extends SortableTreeProvider<ModelWrapper<SolrDocument>, Object> {
210
211        private Integer childrenShown = INITIAL_CHILRDEN_SHOWN;
212
213        public void setChildrenShown(Integer childrenShown) {
214            this.childrenShown = childrenShown;
215        }
216
217        @Override
218        public Iterator<? extends ModelWrapper<SolrDocument>> getRoots() {
219            return Iterators.singletonIterator(
220                    new ModelWrapper<>(
221                            HierarchyPanel.this.getModel()));
222        }
223
224        @Override
225        public boolean hasChildren(ModelWrapper<SolrDocument> node) {
226            if (node.isLimit()) {
227                return false;
228            } else {
229                final Object partCount = node.getModelObject().getFieldValue(FacetConstants.FIELD_HAS_PART_COUNT);
230                return (partCount instanceof Number) && ((Number) partCount).intValue() > 0;
231            }
232        }
233
234        @Override
235        public Iterator<? extends ModelWrapper<SolrDocument>> getChildren(ModelWrapper<SolrDocument> node) {
236            if (node.isLimit()) {
237                return Collections.emptyIterator();
238            } else {
239                final Collection<Object> parts = node.getModelObject().getFieldValues(FacetConstants.FIELD_HAS_PART);
240                final Iterator<Object> objectIterator;
241                if (childrenShown == null) {
242                    // unlimited
243                    objectIterator = parts.iterator();
244                } else {
245                    objectIterator = Iterators.limit(parts.iterator(), childrenShown);
246                }
247                final Iterator<ModelWrapper<SolrDocument>> iterator = Iterators.transform(objectIterator, new Function<Object, ModelWrapper<SolrDocument>>() {
248
249                    @Override
250                    public ModelWrapper<SolrDocument> apply(Object childId) {
251                        final SolrDocument document = documentService.getDocument(childId.toString());
252                        return new ModelWrapper<>(new SolrDocumentModel(document));
253                    }
254                });
255                if (childrenShown != null && parts.size() > childrenShown) {
256                    return Iterators.concat(iterator, // add empty model wrapper to indicate "end of page"
257                            Iterators.singletonIterator(new ModelWrapper<SolrDocument>(parts.size())));
258                } else {
259                    return iterator;
260                }
261            }
262        }
263
264        @Override
265        public IModel<ModelWrapper<SolrDocument>> model(ModelWrapper<SolrDocument> object) {
266            return object;
267        }
268    }
269
270    private class ParentsDataProvider implements IDataProvider<SolrDocument> {
271
272        // used to cache parent count (until detach)
273        private Long size = null;
274
275        @Override
276        public Iterator<? extends SolrDocument> iterator(long first, long count) {
277            final Collection<Object> parentIds = HierarchyPanel.this.getModelObject().getFieldValues(FacetConstants.FIELD_IS_PART_OF);
278            if (parentIds == null) {
279                // no parents, so provide empty list of documents
280                return Collections.emptyIterator();
281            } else {
282                // parents found, convert ID collection into documents list
283                return Iterators.transform(parentIds.iterator(), new Function<Object, SolrDocument>() {
284
285                    @Override
286                    public SolrDocument apply(Object docId) {
287                        return documentService.getDocument(docId.toString());
288                    }
289                });
290            }
291        }
292
293        @Override
294        public long size() {
295            if (size == null) {
296                final Collection<Object> fieldValues = HierarchyPanel.this.getModelObject().getFieldValues(FacetConstants.FIELD_IS_PART_OF);
297                if (fieldValues == null) {
298                    // no parent field
299                    size = 0L;
300                } else {
301                    // any number of parent fields
302                    size = Long.valueOf(fieldValues.size());
303                }
304            }
305            return size;
306        }
307
308        @Override
309        public IModel<SolrDocument> model(SolrDocument object) {
310            if (object == null) {
311                // dangling hasPart references can happen...
312                return new Model<>();
313            } else {
314                return new SolrDocumentModel(object);
315            }
316        }
317
318        @Override
319        public void detach() {
320            size = null;
321        }
322    }
323
324    private class ModelWrapper<T> extends AbstractReadOnlyModel<ModelWrapper<T>> implements Serializable {
325
326        /**
327         * Limit reached?
328         */
329        private final boolean limit;
330
331        /**
332         * Total number of items (if beyond the limit)
333         */
334        private final int count;
335
336        /**
337         * Inner model
338         */
339        private final IModel<T> model;
340
341        public ModelWrapper(int count) {
342            this.limit = true;
343            this.count = count;
344            this.model = null;
345        }
346
347        public ModelWrapper(IModel<T> model) {
348            this.limit = false;
349            this.count = -1;
350            this.model = model;
351        }
352
353        public boolean isLimit() {
354            return limit;
355        }
356
357        public IModel<T> getModel() {
358            return model;
359        }
360
361        public int getCount() {
362            return count;
363        }
364
365        public T getModelObject() {
366            if (model == null) {
367                return null;
368            } else {
369                return model.getObject();
370            }
371        }
372
373        // as IModel<T>
374        @Override
375        public void detach() {
376            if (model != null) {
377                model.detach();
378            }
379        }
380
381        // as IModel<T>
382        @Override
383        public ModelWrapper<T> getObject() {
384            return this;
385        }
386
387        @Override
388        public int hashCode() {
389            int hash = 5;
390            hash = 41 * hash + (this.limit ? 1 : 0);
391            hash = 41 * hash + Objects.hashCode(this.model);
392            return hash;
393        }
394
395        @Override
396        public boolean equals(Object obj) {
397            if (obj == null) {
398                return false;
399            }
400            if (getClass() != obj.getClass()) {
401                return false;
402            }
403            final ModelWrapper<?> other = (ModelWrapper<?>) obj;
404            if (this.limit != other.limit) {
405                return false;
406            }
407            if (!Objects.equals(this.model, other.model)) {
408                return false;
409            }
410            return true;
411        }
412
413    }
414
415}
Note: See TracBrowser for help on using the repository browser.