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.persistence.dao.hibernate.taxon;
|
11
|
|
12
|
import java.util.ArrayList;
|
13
|
import java.util.HashMap;
|
14
|
import java.util.HashSet;
|
15
|
import java.util.List;
|
16
|
import java.util.Map;
|
17
|
import java.util.Set;
|
18
|
import java.util.UUID;
|
19
|
|
20
|
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;
|
21
|
import org.hibernate.query.Query;
|
22
|
import org.springframework.beans.factory.annotation.Autowired;
|
23
|
import org.springframework.beans.factory.annotation.Qualifier;
|
24
|
import org.springframework.stereotype.Repository;
|
25
|
|
26
|
import eu.etaxonomy.cdm.model.common.MarkerType;
|
27
|
import eu.etaxonomy.cdm.model.common.TreeIndex;
|
28
|
import eu.etaxonomy.cdm.model.name.Rank;
|
29
|
import eu.etaxonomy.cdm.model.taxon.Classification;
|
30
|
import eu.etaxonomy.cdm.model.taxon.Taxon;
|
31
|
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
|
32
|
import eu.etaxonomy.cdm.persistence.dao.hibernate.common.IdentifiableDaoBase;
|
33
|
import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
|
34
|
import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
|
35
|
import eu.etaxonomy.cdm.persistence.dto.ClassificationLookupDTO;
|
36
|
|
37
|
/**
|
38
|
* @author a.mueller
|
39
|
* @since 16.06.2009
|
40
|
*/
|
41
|
@Repository
|
42
|
@Qualifier("classificationDaoHibernateImpl")
|
43
|
public class ClassificationDaoHibernateImpl
|
44
|
extends IdentifiableDaoBase<Classification>
|
45
|
implements IClassificationDao {
|
46
|
@SuppressWarnings("unused")
|
47
|
private static final Logger logger = LogManager.getLogger(ClassificationDaoHibernateImpl.class);
|
48
|
|
49
|
@Autowired
|
50
|
private ITaxonNodeDao taxonNodeDao;
|
51
|
|
52
|
public ClassificationDaoHibernateImpl() {
|
53
|
super(Classification.class);
|
54
|
indexedClasses = new Class[1];
|
55
|
indexedClasses[0] = Classification.class;
|
56
|
}
|
57
|
|
58
|
@Override
|
59
|
public List<TaxonNode> listRankSpecificRootNodes(Classification classification, TaxonNode taxonNode, Rank rank,
|
60
|
boolean includeUnpublished, Integer limit, Integer start, List<String> propertyPaths, int queryIndex){
|
61
|
|
62
|
List<TaxonNode> results = new ArrayList<>();
|
63
|
Query<TaxonNode>[] queries = prepareRankSpecificRootNodes(classification, taxonNode, rank, includeUnpublished, false, TaxonNode.class);
|
64
|
|
65
|
// since this method is using two queries sequentially the handling of limit and start
|
66
|
// is a bit more complex
|
67
|
// the prepareRankSpecificRootNodes returns 1 or 2 queries
|
68
|
|
69
|
Query<TaxonNode> q = queries[queryIndex];
|
70
|
if(limit != null) {
|
71
|
q.setMaxResults(limit);
|
72
|
if(start != null) {
|
73
|
q.setFirstResult(start);
|
74
|
}
|
75
|
}
|
76
|
// long start_t = System.currentTimeMillis();
|
77
|
results = q.list();
|
78
|
// System.err.println("dao.listRankSpecificRootNodes() - query[" + queryIndex + "].list() " + (System.currentTimeMillis() - start_t));
|
79
|
// start_t = System.currentTimeMillis();
|
80
|
defaultBeanInitializer.initializeAll(results, propertyPaths);
|
81
|
// System.err.println("dao.listRankSpecificRootNodes() - defaultBeanInitializer.initializeAll() " + (System.currentTimeMillis() - start_t));
|
82
|
|
83
|
return results;
|
84
|
|
85
|
}
|
86
|
|
87
|
@Override
|
88
|
public long[] countRankSpecificRootNodes(Classification classification, TaxonNode subtree, boolean includeUnpublished, Rank rank) {
|
89
|
|
90
|
long[] result = new long[(rank == null ? 1 : 2)];
|
91
|
Query<Long>[] queries = prepareRankSpecificRootNodes(classification, subtree, rank, includeUnpublished, true, Long.class);
|
92
|
int i = 0;
|
93
|
for(Query<Long> q : queries) {
|
94
|
result[i++] = q.uniqueResult();
|
95
|
}
|
96
|
return result;
|
97
|
}
|
98
|
|
99
|
/**
|
100
|
* See <a href="https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/CdmClassificationRankSpecificRootnodes">
|
101
|
* https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/CdmClassificationRankSpecificRootnodes</a>
|
102
|
*
|
103
|
* @param classification
|
104
|
* @param rank
|
105
|
* @return
|
106
|
* one or two Queries as array, depending on the <code>rank</code> parameter:
|
107
|
* <code>rank == null</code>: array with one item, <code>rank != null</code>: array with two items.
|
108
|
*/
|
109
|
private <R extends Object> Query<R>[] prepareRankSpecificRootNodes(Classification classification,
|
110
|
TaxonNode subtree, Rank rank,
|
111
|
boolean includeUnpublished, boolean doCount, Class<R> resultClass) {
|
112
|
|
113
|
Query<R> query1;
|
114
|
Query<R> query2 = null;
|
115
|
|
116
|
String whereClassification = classification != null? " AND tn.classification = :classification " : "";
|
117
|
String whereUnpublished = includeUnpublished? "" : " AND tn.taxon.publish = :publish ";
|
118
|
String whereSubtree = subtree != null ? " AND tn.treeIndex like :treeIndexLike " : "";
|
119
|
TreeIndex treeIndex = TreeIndex.NewInstance(subtree);
|
120
|
String whereHighest =
|
121
|
treeIndex == null ? " tn.parent.parent = null ":
|
122
|
treeIndex.isTreeRoot() ? " tn.parent.treeIndex = :treeIndex ":
|
123
|
" tn.treeIndex = :treeIndex " ;
|
124
|
|
125
|
String selectWhat = doCount ? "COUNT(distinct tn)" : "DISTINCT tn";
|
126
|
|
127
|
String joinFetch = doCount ? "" : " JOIN FETCH tn.taxon t JOIN FETCH t.name n LEFT JOIN FETCH n.rank LEFT JOIN FETCH t.secSource ss LEFT JOIN FETCH ss.citation ";
|
128
|
|
129
|
if(rank == null){
|
130
|
String hql = "SELECT " + selectWhat +
|
131
|
" FROM TaxonNode tn" +
|
132
|
joinFetch +
|
133
|
" WHERE " + whereHighest +
|
134
|
whereClassification + whereUnpublished;
|
135
|
query1 = getSession().createQuery(hql, resultClass);
|
136
|
} else {
|
137
|
// this is for the cases
|
138
|
// - exact match of the ranks
|
139
|
// - rank of root node is lower but it has no parents
|
140
|
String hql1 = "SELECT " + selectWhat +
|
141
|
" FROM TaxonNode tn " +
|
142
|
joinFetch +
|
143
|
" WHERE " +
|
144
|
" (tn.taxon.name.rank = :rank" +
|
145
|
" OR ((tn.taxon.name.rank.orderIndex > :rankOrderIndex) AND (" + whereHighest + "))" +
|
146
|
" )"
|
147
|
+ whereClassification + whereSubtree + whereUnpublished ;
|
148
|
|
149
|
// this is for the case
|
150
|
// - rank of root node is lower and it has a parent with higher rank
|
151
|
String whereParentSubtree = subtree != null ? " AND parent.treeIndex like :treeIndexLike " : "";
|
152
|
String hql2 = "SELECT " + selectWhat +
|
153
|
" FROM TaxonNode tn JOIN tn.parent as parent" +
|
154
|
joinFetch +
|
155
|
" WHERE " +
|
156
|
" (tn.taxon.name.rank.orderIndex > :rankOrderIndex "
|
157
|
+ " AND parent.taxon.name.rank.orderIndex < :rankOrderIndex )"
|
158
|
+ whereClassification + whereSubtree
|
159
|
+ whereParentSubtree + whereUnpublished;
|
160
|
|
161
|
query1 = getSession().createQuery(hql1, resultClass);
|
162
|
query2 = getSession().createQuery(hql2, resultClass);
|
163
|
query1.setParameter("rank", rank);
|
164
|
query1.setParameter("rankOrderIndex", rank.getOrderIndex());
|
165
|
query2.setParameter("rankOrderIndex", rank.getOrderIndex());
|
166
|
}
|
167
|
|
168
|
//parameters
|
169
|
if (classification != null){
|
170
|
query1.setParameter("classification", classification);
|
171
|
if(query2 != null) {
|
172
|
query2.setParameter("classification", classification);
|
173
|
}
|
174
|
}
|
175
|
if (subtree != null){
|
176
|
query1.setParameter("treeIndex", subtree.treeIndex());
|
177
|
if (rank != null){
|
178
|
query1.setParameter("treeIndexLike", subtree.treeIndex()+"%");
|
179
|
}
|
180
|
if(query2 != null) {
|
181
|
query2.setParameter("treeIndexLike", subtree.treeIndex()+"%");
|
182
|
}
|
183
|
}
|
184
|
if (!includeUnpublished){
|
185
|
query1.setParameter("publish", true);
|
186
|
if(query2 != null) {
|
187
|
query2.setParameter("publish", true);
|
188
|
}
|
189
|
}
|
190
|
|
191
|
if(query2 != null) {
|
192
|
return new Query[]{query1, query2};
|
193
|
} else {
|
194
|
return new Query[]{query1};
|
195
|
}
|
196
|
}
|
197
|
|
198
|
@Override
|
199
|
public List<TaxonNode> listChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree, boolean includeUnpublished,
|
200
|
Integer pageSize, Integer pageIndex, List<String> propertyPaths){
|
201
|
|
202
|
Query<TaxonNode> query = prepareListChildrenOf(taxon, classification, subtree, false, includeUnpublished, TaxonNode.class);
|
203
|
|
204
|
addPageSizeAndNumber(query, pageSize, pageIndex);
|
205
|
|
206
|
List<TaxonNode> result = query.list();
|
207
|
//check if array is "empty" (not containing null objects)
|
208
|
if(!result.isEmpty() && result.iterator().next()==null){
|
209
|
return java.util.Collections.emptyList();
|
210
|
}
|
211
|
defaultBeanInitializer.initializeAll(result, propertyPaths);
|
212
|
return result;
|
213
|
}
|
214
|
|
215
|
@Override
|
216
|
public TaxonNode getRootNode(UUID classificationUuid){
|
217
|
String queryString =
|
218
|
" SELECT tn "
|
219
|
+ " FROM TaxonNode tn, Classification c "
|
220
|
+ " WHERE tn = c.rootNode AND c.uuid = :classificationUuid";
|
221
|
|
222
|
Query<TaxonNode> query = getSession().createQuery(queryString, TaxonNode.class);
|
223
|
query.setParameter("classificationUuid", classificationUuid);
|
224
|
|
225
|
List<TaxonNode> results = query.list();
|
226
|
if(results.size()!=1){
|
227
|
return null;
|
228
|
}
|
229
|
return taxonNodeDao.load((results.iterator().next()).getUuid());
|
230
|
}
|
231
|
|
232
|
@Override
|
233
|
public List<TaxonNode> listSiblingsOf(Taxon taxon, Classification classification, boolean includeUnpublished,
|
234
|
Integer pageSize, Integer pageIndex, List<String> propertyPaths){
|
235
|
Query<TaxonNode> query = prepareListSiblingsOf(taxon, classification, includeUnpublished, false, TaxonNode.class);
|
236
|
|
237
|
addPageSizeAndNumber(query, pageSize, pageIndex);
|
238
|
|
239
|
List<TaxonNode> result = query.list();
|
240
|
//check if array is "empty" (not containing null objects)
|
241
|
if(!result.isEmpty() && result.iterator().next()==null){
|
242
|
return java.util.Collections.emptyList();
|
243
|
}
|
244
|
defaultBeanInitializer.initializeAll(result, propertyPaths);
|
245
|
return result;
|
246
|
}
|
247
|
|
248
|
@Override
|
249
|
public Long countChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree,
|
250
|
boolean includeUnpublished){
|
251
|
Query<Long> query = prepareListChildrenOf(taxon, classification, subtree, true, includeUnpublished, Long.class);
|
252
|
Long count = query.uniqueResult();
|
253
|
return count;
|
254
|
}
|
255
|
|
256
|
@Override
|
257
|
public Long countSiblingsOf(Taxon taxon, Classification classification, boolean includeUnpublished){
|
258
|
Query<Long> query = prepareListSiblingsOf(taxon, classification, includeUnpublished, true, Long.class);
|
259
|
Long count = query.uniqueResult();
|
260
|
return count;
|
261
|
}
|
262
|
|
263
|
private <R extends Object> Query<R> prepareListChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree,
|
264
|
boolean doCount, boolean includeUnpublished, Class<R> resultClass){
|
265
|
|
266
|
String selectWhat = doCount ? "COUNT(cn)" : "cn";
|
267
|
|
268
|
String hql = "SELECT " + selectWhat
|
269
|
+ " FROM TaxonNode AS tn "
|
270
|
+ " JOIN tn.classification AS c "
|
271
|
+ " JOIN tn.taxon AS t "
|
272
|
+ " JOIN tn.childNodes AS cn "
|
273
|
+ " WHERE t = :taxon "
|
274
|
+ " AND c = :classification";
|
275
|
if (!includeUnpublished){
|
276
|
hql += " AND cn.taxon.publish = :publish ";
|
277
|
}
|
278
|
if (subtree != null){
|
279
|
hql += " AND tn.treeIndex like :treeIndexLike ";
|
280
|
}
|
281
|
Query<R> query = getSession().createQuery(hql, resultClass);
|
282
|
query.setParameter("taxon", taxon);
|
283
|
query.setParameter("classification", classification);
|
284
|
if (!includeUnpublished){
|
285
|
query.setParameter("publish", Boolean.TRUE);
|
286
|
}
|
287
|
if (subtree != null){
|
288
|
query.setParameter("treeIndexLike", subtree.treeIndexLike());
|
289
|
}
|
290
|
return query;
|
291
|
}
|
292
|
|
293
|
private <R extends Object> Query<R> prepareListSiblingsOf(Taxon taxon, Classification classification,
|
294
|
boolean includeUnpublished, boolean doCount, Class<R> resultClass){
|
295
|
|
296
|
String selectWhat = doCount ? "COUNT(tn)" : "tn";
|
297
|
String whereUnpublished = includeUnpublished? "" : " AND t.publish = :publish ";
|
298
|
|
299
|
String subSelect =
|
300
|
" SELECT tn.parent "
|
301
|
+ " FROM TaxonNode AS tn "
|
302
|
+ " JOIN tn.classification AS c "
|
303
|
+ " JOIN tn.taxon AS t "
|
304
|
+ " WHERE t = :taxon "
|
305
|
+ " AND c = :classification "
|
306
|
+ whereUnpublished;
|
307
|
String hql = " SELECT " + selectWhat
|
308
|
+ " FROM TaxonNode as tn "
|
309
|
+ " WHERE tn.parent IN ( " + subSelect + ")";
|
310
|
Query<R> query = getSession().createQuery(hql, resultClass);
|
311
|
query.setParameter("taxon", taxon);
|
312
|
query.setParameter("classification", classification);
|
313
|
if (!includeUnpublished){
|
314
|
query.setParameter("publish", true);
|
315
|
}
|
316
|
return query;
|
317
|
}
|
318
|
|
319
|
|
320
|
@Override
|
321
|
public UUID delete(Classification persistentObject){
|
322
|
//delete all child nodes, then delete the tree
|
323
|
if (persistentObject.getRootNode() != null){
|
324
|
List<TaxonNode> nodes = persistentObject.getChildNodes();
|
325
|
List<TaxonNode> nodesTmp = new ArrayList<>(nodes);
|
326
|
for(TaxonNode node : nodesTmp){
|
327
|
persistentObject.deleteChildNode(node, true);
|
328
|
taxonNodeDao.delete(node, true);
|
329
|
}
|
330
|
}
|
331
|
|
332
|
TaxonNode rootNode = persistentObject.getRootNode();
|
333
|
persistentObject.removeRootNode();
|
334
|
taxonNodeDao.delete(rootNode);
|
335
|
super.delete(persistentObject);
|
336
|
|
337
|
return persistentObject.getUuid();
|
338
|
}
|
339
|
|
340
|
@Override
|
341
|
public ClassificationLookupDTO classificationLookup(Classification classification) {
|
342
|
|
343
|
ClassificationLookupDTO classificationLookupDTO = new ClassificationLookupDTO(classification);
|
344
|
|
345
|
String hql =
|
346
|
" SELECT t.id, n.rank, tp.id "
|
347
|
+ " FROM TaxonNode AS tn "
|
348
|
+ " JOIN tn.classification AS c "
|
349
|
+ " JOIN tn.taxon AS t "
|
350
|
+ " JOIN t.name AS n "
|
351
|
+ " LEFT JOIN tn.parent AS tnp "
|
352
|
+ " LEFT JOIN tnp.taxon as tp "
|
353
|
+ " WHERE c = :classification";
|
354
|
|
355
|
Query<Object[]> query = getSession().createQuery(hql, Object[].class);
|
356
|
query.setParameter("classification", classification);
|
357
|
|
358
|
List<Object[]> result = query.list();
|
359
|
for(Object[] row : result) {
|
360
|
Integer parentId = null;
|
361
|
parentId = (Integer) row[2];
|
362
|
classificationLookupDTO.add((Integer)row[0], (Rank)row[1], parentId);
|
363
|
}
|
364
|
|
365
|
return classificationLookupDTO ;
|
366
|
}
|
367
|
|
368
|
@Override
|
369
|
public Map<UUID, TreeIndex> treeIndexForTaxonUuids(UUID classificationUuid,
|
370
|
List<UUID> taxonUuids) {
|
371
|
String hql = " SELECT t.uuid, tn.treeIndex "
|
372
|
+ " FROM Taxon t JOIN t.taxonNodes tn "
|
373
|
+ " WHERE (1=1)"
|
374
|
+ " AND tn.classification.uuid = :classificationUuid "
|
375
|
+ " AND t.uuid IN (:taxonUuids) "
|
376
|
;
|
377
|
Query<Object[]> query = getSession().createQuery(hql, Object[].class);
|
378
|
query.setParameter("classificationUuid", classificationUuid);
|
379
|
query.setParameterList("taxonUuids", taxonUuids);
|
380
|
|
381
|
Map<UUID, TreeIndex> result = new HashMap<>();
|
382
|
List<Object[]> list = query.list();
|
383
|
for (Object[] o : list){
|
384
|
result.put((UUID)o[0], TreeIndex.NewInstance((String)o[1]));
|
385
|
}
|
386
|
return result;
|
387
|
}
|
388
|
|
389
|
@Override
|
390
|
public Set<TreeIndex> getMarkedTreeIndexes(MarkerType markerType, Boolean flag){
|
391
|
String hql = " SELECT tn.treeIndex "
|
392
|
+ " FROM Taxon t "
|
393
|
+ " JOIN t.taxonNodes tn "
|
394
|
+ " JOIN t.markers m "
|
395
|
+ " WHERE (1=1)"
|
396
|
+ " AND m.markerType = :markerType "
|
397
|
;
|
398
|
if (flag != null){
|
399
|
hql += " AND m.flag = :flag ";
|
400
|
|
401
|
}
|
402
|
|
403
|
Query<String> query = getSession().createQuery(hql, String.class);
|
404
|
if (flag != null){
|
405
|
query.setParameter("flag", flag);
|
406
|
}
|
407
|
query.setParameter("markerType", markerType);
|
408
|
|
409
|
Set<TreeIndex> result = new HashSet<>();
|
410
|
|
411
|
List<String> list = query.list();
|
412
|
for (String o : list){
|
413
|
result.add(TreeIndex.NewInstance(o));
|
414
|
}
|
415
|
return result;
|
416
|
}
|
417
|
|
418
|
@Override
|
419
|
public Map<UUID, UUID> getTaxonNodeUuidByTaxonUuid(UUID classificationUuid, List<UUID> taxonUuids) {
|
420
|
String hql = " SELECT t.uuid, tn.uuid "
|
421
|
+ " FROM Taxon t JOIN t.taxonNodes tn "
|
422
|
+ " WHERE (1=1)"
|
423
|
+ " AND tn.classification.uuid = :classificationUuid "
|
424
|
+ " AND t.uuid IN (:taxonUuids) "
|
425
|
;
|
426
|
Query<Object[]> query = getSession().createQuery(hql, Object[].class);
|
427
|
query.setParameter("classificationUuid", classificationUuid);
|
428
|
query.setParameterList("taxonUuids", taxonUuids);
|
429
|
|
430
|
Map<UUID, UUID> result = new HashMap<>();
|
431
|
List<Object[]> list = query.list();
|
432
|
for (Object[] o : list){
|
433
|
result.put((UUID)o[0], (UUID)o[1]);
|
434
|
}
|
435
|
return result;
|
436
|
}
|
437
|
}
|