Project

General

Profile

Download (11 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9

    
10
package eu.etaxonomy.cdm.api.service;
11

    
12
import java.io.Serializable;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.Collections;
16
import java.util.Comparator;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Set;
20

    
21
import org.apache.log4j.Logger;
22
import org.hibernate.proxy.HibernateProxy;
23

    
24
import eu.etaxonomy.cdm.api.utility.DescriptionUtility;
25
import eu.etaxonomy.cdm.api.utility.DistributionOrder;
26
import eu.etaxonomy.cdm.common.Tree;
27
import eu.etaxonomy.cdm.common.TreeNode;
28
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
29
import eu.etaxonomy.cdm.model.common.Marker;
30
import eu.etaxonomy.cdm.model.common.MarkerType;
31
import eu.etaxonomy.cdm.model.description.Distribution;
32
import eu.etaxonomy.cdm.model.location.NamedArea;
33
import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
34
import eu.etaxonomy.cdm.persistence.dao.term.IDefinedTermDao;
35

    
36
/**
37
 * TODO javadoc.
38
 *
39
 * There is a somehow similar implementation in {@link eu.etaxonomy.cdm.model.location.NamedArea}
40
 */
41
public class DistributionTree extends Tree<Set<Distribution>, NamedArea>{
42

    
43
    public static final Logger logger = Logger.getLogger(DistributionTree.class);
44

    
45
    private final IDefinedTermDao termDao;
46

    
47
    public DistributionTree(IDefinedTermDao termDao){
48
        TreeNode<Set<Distribution>, NamedArea> rootElement = new TreeNode<Set<Distribution>, NamedArea>();
49
        List<TreeNode<Set<Distribution>, NamedArea>> children = new ArrayList<TreeNode<Set<Distribution>, NamedArea>>();
50
        rootElement.setChildren(children);
51
        setRootElement(rootElement);
52
        this.termDao = termDao;
53
    }
54

    
55
    /**
56
     * @param parentNode
57
     * @param nodeToFind
58
     * @return false if the node was not found
59
     */
60
    public boolean hasChildNode(TreeNode<Set<Distribution>, NamedArea> parentNode, NamedArea nodeID) {
61
        return findChildNode(parentNode, nodeID) != null;
62
    }
63

    
64
    /**
65
     * Returns the (first) child node (of type TreeNode) with the given nodeID.
66
     * @param parentNode
67
     * @param nodeToFind
68
     * @return the found node or null
69
     */
70
    public TreeNode<Set<Distribution>, NamedArea> findChildNode(TreeNode<Set<Distribution>, NamedArea> parentNode, NamedArea  nodeID) {
71
        if (parentNode.getChildren() == null) {
72
            return null;
73
        }
74

    
75
        for (TreeNode<Set<Distribution>, NamedArea> node : parentNode.getChildren()) {
76
            if (node.getNodeId().equals(nodeID)) {
77
                return node;
78
            }
79
        }
80
        return null;
81
    }
82

    
83
    /**
84
     * @param distList
85
     * @param omitLevels
86
     * @param hiddenAreaMarkerTypes
87
     *      Areas not associated to a Distribution in the {@code distList} are detected as fall back area
88
     *      if they are having a {@link Marker} with one of the specified {@link MarkerType}s. Areas identified
89
     *      as such are omitted from the hierarchy and the sub areas are moving one level up.
90
     *      For more details on fall back areas see <b>Marked area filter</b> of
91
     *      {@link DescriptionUtility#filterDistributions(Collection, Set, boolean, boolean, boolean)}.
92
     */
93
    public void orderAsTree(Collection<Distribution> distList,
94
            Set<NamedAreaLevel> omitLevels,
95
            Set<MarkerType> hiddenAreaMarkerTypes){
96

    
97
        Set<NamedArea> areas = new HashSet<NamedArea>(distList.size());
98
        for (Distribution distribution : distList) {
99
            areas.add(distribution.getArea());
100
        }
101
        // preload all areas which are a parent of another one, this is a performance improvement
102
        loadAllParentAreas(areas);
103

    
104
        Set<Integer> omitLevelIds = new HashSet<Integer>(omitLevels.size());
105
        for(NamedAreaLevel level : omitLevels) {
106
            omitLevelIds.add(level.getId());
107
        }
108

    
109
        for (Distribution distribution : distList) {
110
            // get path through area hierarchy
111
            List<NamedArea> namedAreaPath = getAreaLevelPath(distribution.getArea(), omitLevelIds, areas, hiddenAreaMarkerTypes);
112
            addDistributionToSubTree(distribution, namedAreaPath, this.getRootElement());
113
        }
114
    }
115

    
116
    /**
117
     * This method will cause all parent areas to be loaded into the session cache to that
118
     * all initialization of the NamedArea term instances in necessary. This improves the
119
     * performance of the tree building
120
     */
121
    private void loadAllParentAreas(Set<NamedArea> areas) {
122

    
123
        List<NamedArea> parentAreas = null;
124
        Set<NamedArea> childAreas = new HashSet<NamedArea>(areas.size());
125
        for(NamedArea areaProxy : areas) {
126
            NamedArea deproxied = HibernateProxyHelper.deproxy(areaProxy, NamedArea.class);
127
            childAreas.add(deproxied);
128
        }
129

    
130
        if(!childAreas.isEmpty()) {
131
            parentAreas = termDao.getPartOf(childAreas, null, null, null);
132
            childAreas.clear();
133
            childAreas.addAll(parentAreas);
134
        }
135

    
136
    }
137

    
138
    public void recursiveSortChildren(DistributionOrder distributionOrder){
139
        if (distributionOrder == null){
140
            distributionOrder = DistributionOrder.getDefault();
141
        }
142
        _recursiveSortChildren(this.getRootElement(), distributionOrder.getComparator());
143
    }
144

    
145
    private void _recursiveSortChildren(TreeNode<Set<Distribution>, NamedArea> treeNode,
146
            Comparator<TreeNode<Set<Distribution>, NamedArea>> comparator){
147
        if (treeNode.children == null) {
148
            //nothing => stop condition
149
            return;
150
        }else {
151
            Collections.sort(treeNode.children, comparator);
152
            for (TreeNode<Set<Distribution>, NamedArea> child : treeNode.children) {
153
                _recursiveSortChildren(child, comparator);
154
            }
155
        }
156
    }
157

    
158
    /**
159
     * Adds the given <code>distributionElement</code> to the sub tree defined by
160
     * the <code>root</code>.
161
     *
162
     * @param distribution
163
     *            the {@link Distribution} to add to the tree at the position
164
     *            according to the NamedArea hierarchy.
165
     * @param namedAreaPath
166
     *            the path to the root of the NamedArea hierarchy starting the
167
     *            area used in the given <code>distributionElement</code>. The
168
     *            hierarchy is defined by the {@link NamedArea#getPartOf()}
169
     *            relationships
170
     * @param root
171
     *            root element of the sub tree to which the
172
     *            <code>distributionElement</code> is to be added
173
     */
174
    private void addDistributionToSubTree(Distribution distribution,
175
            List<NamedArea> namedAreaPath,
176
            TreeNode<Set<Distribution>, NamedArea> root){
177

    
178

    
179
        //if the list to merge is empty finish the execution
180
        if (namedAreaPath.isEmpty()) {
181
            return;
182
        }
183

    
184
        //getting the highest area and inserting it into the tree
185
        NamedArea highestArea = namedAreaPath.get(0);
186

    
187

    
188
        TreeNode<Set<Distribution>, NamedArea> child = findChildNode(root, highestArea);
189
        if (child == null) {
190
            // the highestDistNode is not yet in the set of children, so we add it
191
            child = new TreeNode<Set<Distribution>, NamedArea>(highestArea);
192
            child.setData(new HashSet<Distribution>());
193
            root.addChild(child);
194
        }
195

    
196
        // add another element to the list of data
197
        if(namedAreaPath.get(0).equals(distribution.getArea())){
198
            if(namedAreaPath.size() > 1){
199
                logger.error("there seems to be something wrong with the area hierarchy");
200
            }
201
            child.getData().add(distribution);
202
            return; // done!
203
        }
204

    
205
        // Recursively proceed into the namedAreaPath to merge the next node
206
        List<NamedArea> newList = namedAreaPath.subList(1, namedAreaPath.size());
207
        addDistributionToSubTree(distribution, newList, child);
208
    }
209

    
210

    
211
    /**
212
     * Areas for which no distribution data is available and which are marked as hidden (Marker) are omitted, see #5112
213
     *
214
     * @param area
215
     * @param distributionAreas the areas for which distribution data exists (after filtering by
216
     *  {@link eu.etaxonomy.cdm.api.utility.DescriptionUtility#filterDistributions()} )
217
     * @param hiddenAreaMarkerTypes
218
     *      Areas not associated to a Distribution in the {@code distList} are detected as fall back area
219
     *      if they are having a {@link Marker} with one of the specified {@link MarkerType}s. Areas identified as such
220
     *      are omitted. For more details on fall back areas see <b>Marked area filter</b> of
221
     *      {@link DescriptionUtility#filterDistributions(Collection, Set, boolean, boolean, boolean)}.
222
     * @param omitLevels
223
     * @return the path through area hierarchy from the <code>area</code> given as parameter to the root
224
     */
225
    private List<NamedArea> getAreaLevelPath(NamedArea area, Set<Integer> omitLevelIds, Set<NamedArea> distributionAreas, Set<MarkerType> hiddenAreaMarkerTypes){
226
        List<NamedArea> result = new ArrayList<NamedArea>();
227
        if (!matchesLevels(area, omitLevelIds)){
228
            result.add(area);
229
        }
230
        // logging special case in order to help solving ticket #3891 (ordered distributions provided by portal/description/${uuid}/DistributionTree randomly broken)
231

    
232
        if(area.getPartOf() == null) {
233
            StringBuilder hibernateInfo = new StringBuilder();
234
            hibernateInfo.append(", area is of type: ").append(area.getClass());
235
            if(area instanceof HibernateProxy){
236
                hibernateInfo.append(" target object is ").append(HibernateProxyHelper.getClassWithoutInitializingProxy(area));
237
            }
238
            logger.warn("area.partOf is NULL for " + area.getLabel() + hibernateInfo.toString());
239
        }
240
        while (area.getPartOf() != null) {
241
            area = area.getPartOf();
242
            if (!matchesLevels(area, omitLevelIds)){
243
                if(!distributionAreas.contains(area) &&
244
                    DescriptionUtility.checkAreaMarkedHidden(hiddenAreaMarkerTypes, area)) {
245
                    if(logger.isDebugEnabled()) {
246
                        logger.debug("positive fallback area detection, skipping " + area );
247
                    }
248
                } else {
249
                    result.add(0, area);
250
                }
251
            }
252
        }
253

    
254
        return result;
255
    }
256

    
257
    /**
258
     * @param area
259
     * @param levels
260
     * @return
261
     */
262
    private boolean matchesLevels(NamedArea area, Set<Integer> omitLevelIds) {
263
        if(omitLevelIds.isEmpty()) {
264
            return false;
265
        }
266
        Serializable areaLevelId;
267
        NamedAreaLevel areaLevel = area.getLevel();
268
        if (areaLevel instanceof HibernateProxy) {
269
            areaLevelId = ((HibernateProxy) areaLevel).getHibernateLazyInitializer().getIdentifier();
270
        } else {
271
            areaLevelId = areaLevel==null ? null : areaLevel.getId();
272
        }
273
        return omitLevelIds.contains(areaLevelId);
274
    }
275

    
276

    
277

    
278
}
(14-14/100)