Merge branch 'release/5.45.0'
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / dao / hibernate / taxon / ClassificationDaoHibernateImpl.java
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 package eu.etaxonomy.cdm.persistence.dao.hibernate.taxon;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.UUID;
18
19 import org.apache.logging.log4j.LogManager;
20 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 import eu.etaxonomy.cdm.persistence.dto.SortableTaxonNodeQueryResult;
37 import eu.etaxonomy.cdm.persistence.dto.TaxonNodeDto;
38
39 /**
40 * @author a.mueller
41 * @since 16.06.2009
42 */
43 @Repository
44 @Qualifier("classificationDaoHibernateImpl")
45 public class ClassificationDaoHibernateImpl
46 extends IdentifiableDaoBase<Classification>
47 implements IClassificationDao {
48 @SuppressWarnings("unused")
49 private static final Logger logger = LogManager.getLogger();
50
51 @Autowired
52 private ITaxonNodeDao taxonNodeDao;
53
54 public ClassificationDaoHibernateImpl() {
55 super(Classification.class);
56 indexedClasses = new Class[1];
57 indexedClasses[0] = Classification.class;
58 }
59
60 @Override
61 public List<TaxonNode> listRankSpecificRootNodes(Classification classification, TaxonNode taxonNode, Rank rank,
62 boolean includeUnpublished, Integer limit, Integer start, List<String> propertyPaths, int queryIndex){
63
64 List<TaxonNode> results = new ArrayList<>();
65 Query<TaxonNode>[] queries = prepareRankSpecificRootNodes(classification, taxonNode, rank, includeUnpublished, false, TaxonNode.class);
66
67 // since this method is using two queries sequentially the handling of limit and start
68 // is a bit more complex
69 // the prepareRankSpecificRootNodes returns 1 or 2 queries
70
71 Query<TaxonNode> q = queries[queryIndex];
72 if(limit != null) {
73 q.setMaxResults(limit);
74 if(start != null) {
75 q.setFirstResult(start);
76 }
77 }
78 // long start_t = System.currentTimeMillis();
79 results = q.list();
80 // System.err.println("dao.listRankSpecificRootNodes() - query[" + queryIndex + "].list() " + (System.currentTimeMillis() - start_t));
81 // start_t = System.currentTimeMillis();
82 defaultBeanInitializer.initializeAll(results, propertyPaths);
83 // System.err.println("dao.listRankSpecificRootNodes() - defaultBeanInitializer.initializeAll() " + (System.currentTimeMillis() - start_t));
84
85 return results;
86
87 }
88
89 @Override
90 public long[] countRankSpecificRootNodes(Classification classification, TaxonNode subtree, boolean includeUnpublished, Rank rank) {
91
92 long[] result = new long[(rank == null ? 1 : 2)];
93 Query<Long>[] queries = prepareRankSpecificRootNodes(classification, subtree, rank, includeUnpublished, true, Long.class);
94 int i = 0;
95 for(Query<Long> q : queries) {
96 result[i++] = q.uniqueResult();
97 }
98 return result;
99 }
100
101 /**
102 * See <a href="https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/CdmClassificationRankSpecificRootnodes">
103 * https://dev.e-taxonomy.eu/redmine/projects/edit/wiki/CdmClassificationRankSpecificRootnodes</a>
104 *
105 * @param classification
106 * @param rank
107 * @return
108 * one or two Queries as array, depending on the <code>rank</code> parameter:
109 * <code>rank == null</code>: array with one item, <code>rank != null</code>: array with two items.
110 */
111 private <R extends Object> Query<R>[] prepareRankSpecificRootNodes(Classification classification,
112 TaxonNode subtree, Rank rank,
113 boolean includeUnpublished, boolean doCount, Class<R> resultClass) {
114
115 Query<R> query1;
116 Query<R> query2 = null;
117
118 String whereClassification = classification != null? " AND tn.classification = :classification " : "";
119 String whereUnpublished = includeUnpublished? "" : " AND tn.taxon.publish = :publish ";
120 String whereSubtree = subtree != null ? " AND tn.treeIndex like :treeIndexLike " : "";
121 TreeIndex treeIndex = TreeIndex.NewInstance(subtree);
122 String whereHighest =
123 treeIndex == null ? " tn.parent.parent = null ":
124 treeIndex.isTreeRoot() ? " tn.parent.treeIndex = :treeIndex ":
125 " tn.treeIndex = :treeIndex " ;
126
127 String selectWhat = doCount ? "COUNT(distinct tn)" : "DISTINCT tn";
128
129 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 ";
130
131 if(rank == null){
132 String hql = "SELECT " + selectWhat +
133 " FROM TaxonNode tn" +
134 joinFetch +
135 " WHERE " + whereHighest +
136 whereClassification + whereUnpublished;
137 query1 = getSession().createQuery(hql, resultClass);
138 } else {
139 // this is for the cases
140 // - exact match of the ranks
141 // - rank of root node is lower but it has no parents
142 String hql1 = "SELECT " + selectWhat +
143 " FROM TaxonNode tn " +
144 joinFetch +
145 " WHERE " +
146 " (tn.taxon.name.rank = :rank" +
147 " OR ((tn.taxon.name.rank.orderIndex > :rankOrderIndex) AND (" + whereHighest + "))" +
148 " )"
149 + whereClassification + whereSubtree + whereUnpublished ;
150
151 // this is for the case
152 // - rank of root node is lower and it has a parent with higher rank
153 String whereParentSubtree = subtree != null ? " AND parent.treeIndex like :treeIndexLike " : "";
154 String hql2 = "SELECT " + selectWhat +
155 " FROM TaxonNode tn JOIN tn.parent as parent" +
156 joinFetch +
157 " WHERE " +
158 " (tn.taxon.name.rank.orderIndex > :rankOrderIndex "
159 + " AND parent.taxon.name.rank.orderIndex < :rankOrderIndex )"
160 + whereClassification + whereSubtree
161 + whereParentSubtree + whereUnpublished;
162
163 query1 = getSession().createQuery(hql1, resultClass);
164 query2 = getSession().createQuery(hql2, resultClass);
165 query1.setParameter("rank", rank);
166 query1.setParameter("rankOrderIndex", rank.getOrderIndex());
167 query2.setParameter("rankOrderIndex", rank.getOrderIndex());
168 }
169
170 //parameters
171 if (classification != null){
172 query1.setParameter("classification", classification);
173 if(query2 != null) {
174 query2.setParameter("classification", classification);
175 }
176 }
177 if (subtree != null){
178 query1.setParameter("treeIndex", subtree.treeIndex());
179 if (rank != null){
180 query1.setParameter("treeIndexLike", subtree.treeIndex()+"%");
181 }
182 if(query2 != null) {
183 query2.setParameter("treeIndexLike", subtree.treeIndex()+"%");
184 }
185 }
186 if (!includeUnpublished){
187 query1.setParameter("publish", true);
188 if(query2 != null) {
189 query2.setParameter("publish", true);
190 }
191 }
192
193 if(query2 != null) {
194 return new Query[]{query1, query2};
195 } else {
196 return new Query[]{query1};
197 }
198 }
199
200 @Override
201 public List<TaxonNodeDto> listChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree,
202 boolean includeUnpublished, Integer pageSize, Integer pageIndex){
203
204 Query<SortableTaxonNodeQueryResult> query = prepareListChildrenOf(taxon, classification,
205 subtree, QueryType.DTO, includeUnpublished, SortableTaxonNodeQueryResult.class);
206
207 addPageSizeAndNumber(query, pageSize, pageIndex);
208
209 List<SortableTaxonNodeQueryResult> queryResult = query.list();
210 //check if array is "empty" (not containing null objects) //copied from non-DTO version. Necessary here?
211 if(!queryResult.isEmpty() && queryResult.iterator().next()==null){
212 return java.util.Collections.emptyList();
213 }
214 List<TaxonNodeDto> result = SortableTaxonNodeQueryResult.toTaxonNodeDtoList(queryResult);
215 return result;
216 }
217
218 @Override
219 public List<TaxonNode> listChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree, boolean includeUnpublished,
220 Integer pageSize, Integer pageIndex, List<String> propertyPaths){
221
222 Query<TaxonNode> query = prepareListChildrenOf(taxon, classification,
223 subtree, QueryType.INSTANCE, includeUnpublished, TaxonNode.class);
224
225 addPageSizeAndNumber(query, pageSize, pageIndex);
226 // query.setHint( "org.hibernate.readOnly", true ); //TODO does not seem to have an effect. (https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql-read-only-entities)
227 List<TaxonNode> result = query.list();
228 //check if array is "empty" (not containing null objects)
229 if(!result.isEmpty() && result.iterator().next()==null){
230 return java.util.Collections.emptyList();
231 }
232 defaultBeanInitializer.initializeAll(result, propertyPaths);
233 return result;
234 }
235
236 @Override
237 public TaxonNode getRootNode(UUID classificationUuid){
238 String queryString =
239 " SELECT tn "
240 + " FROM TaxonNode tn, Classification c "
241 + " WHERE tn = c.rootNode AND c.uuid = :classificationUuid";
242
243 Query<TaxonNode> query = getSession().createQuery(queryString, TaxonNode.class);
244 query.setParameter("classificationUuid", classificationUuid);
245
246 List<TaxonNode> results = query.list();
247 if(results.size()!=1){
248 return null;
249 }
250 return taxonNodeDao.load((results.iterator().next()).getUuid());
251 }
252
253 @Override
254 public List<TaxonNode> listSiblingsOf(Taxon taxon, Classification classification, boolean includeUnpublished,
255 Integer pageSize, Integer pageIndex, List<String> propertyPaths){
256 Query<TaxonNode> query = prepareListSiblingsOf(taxon, classification, includeUnpublished, false, TaxonNode.class);
257
258 addPageSizeAndNumber(query, pageSize, pageIndex);
259
260 List<TaxonNode> result = query.list();
261 //check if array is "empty" (not containing null objects)
262 if(!result.isEmpty() && result.iterator().next()==null){
263 return java.util.Collections.emptyList();
264 }
265 defaultBeanInitializer.initializeAll(result, propertyPaths);
266 return result;
267 }
268
269 @Override
270 public Long countChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree,
271 boolean includeUnpublished){
272 Query<Long> query = prepareListChildrenOf(taxon, classification, subtree,
273 QueryType.COUNT, includeUnpublished, Long.class);
274 Long count = query.uniqueResult();
275 return count;
276 }
277
278 @Override
279 public Long countSiblingsOf(Taxon taxon, Classification classification, boolean includeUnpublished){
280 Query<Long> query = prepareListSiblingsOf(taxon, classification, includeUnpublished, true, Long.class);
281 Long count = query.uniqueResult();
282 return count;
283 }
284
285 private enum QueryType {COUNT, DTO, INSTANCE}
286
287 public class TaxonNodeQueryResult {
288 public UUID nodeUuid; Integer nodeId; UUID taxonUuid; String taxonTitleCache;
289
290 public TaxonNodeQueryResult(String titleCache) {
291 }
292 public TaxonNodeQueryResult(UUID nodeUuid) {
293 }
294 public TaxonNodeQueryResult(UUID nodeUuid, int nodeId, UUID taxonUuid, String taxonTitleCache) {
295 this.nodeUuid = nodeUuid;
296 this.nodeId = nodeId;
297 this.taxonUuid = taxonUuid;
298 this.taxonTitleCache = taxonTitleCache;
299 }
300
301 }
302
303 private <R extends Object> Query<R> prepareListChildrenOf(Taxon taxon, Classification classification, TaxonNode subtree,
304 QueryType queryType, boolean includeUnpublished, Class<R> resultClass){
305
306 String selectWhat = queryType == QueryType.COUNT ? "COUNT(cn)"
307 : queryType == QueryType.INSTANCE ? "cn"
308 : (" new " +SortableTaxonNodeQueryResult.class.getName()+"("
309 + "cn.uuid "
310 + ", cn.id "
311 + ", cn.treeIndex "
312 + ", ct.uuid "
313 + ", ct.titleCache "
314 + ", ct.name.titleCache "
315 + ", r "
316 + ", tn.uuid " //parent.uuid
317 + ", index(cn) " //sortIndex,"
318 + ", c.uuid "
319 + ", ct.publish "
320 + ", cn.status "
321 + ", cn.countChildren"
322 + ", sec.uuid"
323 + ", ct.name.nameType"
324 + ", ct.name.genusOrUninomial"
325 + ", ct.name.infraGenericEpithet"
326 + ", ct.name.specificEpithet"
327 + ", ct.name.infraSpecificEpithet"
328 + ", ct.name.appendedPhrase"
329 + ", ct.name.protectedTitleCache"
330 + ", ct.name.protectedNameCache"
331 + ", ct.name.nameCache"
332 + ", ct.name.authorshipCache"
333 + ", ct.name.publicationYear"
334 + ", ct.name.monomHybrid"
335 + ", ct.name.binomHybrid"
336 + ", ct.name.trinomHybrid"
337
338 // + ", entry(cn.statusNote) " //cn.statusNote
339 // + ", cn.taxon.name.rank.orderIndex "
340 // + "cn.taxon.name.rank.titleCache " //TODO maybe ...rank.representations (?)
341 + ")"
342 )
343
344 ; //TODO language dependent;
345
346 String hql = "SELECT " + selectWhat
347 + " FROM TaxonNode AS tn "
348 + " JOIN tn.classification AS c "
349 + " JOIN tn.taxon AS t "
350 + " JOIN tn.childNodes AS cn "
351 + " JOIN cn.taxon AS ct "
352 + " JOIN ct.name AS ctn "
353 + " LEFT JOIN ctn.rank r "
354 + " LEFT JOIN ct.secSource ss "
355 + " LEFT JOIN ss.citation sec "
356 + " WHERE t = :taxon "
357 + " AND c = :classification";
358 if (!includeUnpublished){
359 hql += " AND cn.taxon.publish = :publish ";
360 }
361 if (subtree != null){
362 hql += " AND tn.treeIndex like :treeIndexLike ";
363 }
364 Query<R> query = getSession().createQuery(hql, resultClass);
365 query.setParameter("taxon", taxon);
366 query.setParameter("classification", classification);
367 if (!includeUnpublished){
368 query.setParameter("publish", Boolean.TRUE);
369 }
370 if (subtree != null){
371 query.setParameter("treeIndexLike", subtree.treeIndexLike());
372 }
373 return query;
374 }
375
376 private <R extends Object> Query<R> prepareListSiblingsOf(Taxon taxon, Classification classification,
377 boolean includeUnpublished, boolean doCount, Class<R> resultClass){
378
379 String selectWhat = doCount ? "COUNT(tn)" : "tn";
380 String whereUnpublished = includeUnpublished? "" : " AND t.publish = :publish ";
381
382 String subSelect =
383 " SELECT tn.parent "
384 + " FROM TaxonNode AS tn "
385 + " JOIN tn.classification AS c "
386 + " JOIN tn.taxon AS t "
387 + " WHERE t = :taxon "
388 + " AND c = :classification "
389 + whereUnpublished;
390 String hql = " SELECT " + selectWhat
391 + " FROM TaxonNode as tn "
392 + " WHERE tn.parent IN ( " + subSelect + ")";
393 Query<R> query = getSession().createQuery(hql, resultClass);
394 query.setParameter("taxon", taxon);
395 query.setParameter("classification", classification);
396 if (!includeUnpublished){
397 query.setParameter("publish", true);
398 }
399 return query;
400 }
401
402
403 @Override
404 public UUID delete(Classification persistentObject){
405 //delete all child nodes, then delete the tree
406 if (persistentObject.getRootNode() != null){
407 List<TaxonNode> nodes = persistentObject.getChildNodes();
408 List<TaxonNode> nodesTmp = new ArrayList<>(nodes);
409 for(TaxonNode node : nodesTmp){
410 persistentObject.deleteChildNode(node, true);
411 taxonNodeDao.delete(node, true);
412 }
413 }
414
415 TaxonNode rootNode = persistentObject.getRootNode();
416 persistentObject.removeRootNode();
417 taxonNodeDao.delete(rootNode);
418 super.delete(persistentObject);
419
420 return persistentObject.getUuid();
421 }
422
423 @Override
424 public ClassificationLookupDTO classificationLookup(Classification classification) {
425
426 ClassificationLookupDTO classificationLookupDTO = new ClassificationLookupDTO(classification);
427
428 String hql =
429 " SELECT t.id, n.rank, tp.id "
430 + " FROM TaxonNode AS tn "
431 + " JOIN tn.classification AS c "
432 + " JOIN tn.taxon AS t "
433 + " JOIN t.name AS n "
434 + " LEFT JOIN tn.parent AS tnp "
435 + " LEFT JOIN tnp.taxon as tp "
436 + " WHERE c = :classification";
437
438 Query<Object[]> query = getSession().createQuery(hql, Object[].class);
439 query.setParameter("classification", classification);
440
441 List<Object[]> result = query.list();
442 for(Object[] row : result) {
443 Integer parentId = null;
444 parentId = (Integer) row[2];
445 classificationLookupDTO.add((Integer)row[0], (Rank)row[1], parentId);
446 }
447
448 return classificationLookupDTO ;
449 }
450
451 @Override
452 public Map<UUID, TreeIndex> treeIndexForTaxonUuids(UUID classificationUuid,
453 List<UUID> taxonUuids) {
454 String hql = " SELECT t.uuid, tn.treeIndex "
455 + " FROM Taxon t JOIN t.taxonNodes tn "
456 + " WHERE (1=1)"
457 + " AND tn.classification.uuid = :classificationUuid "
458 + " AND t.uuid IN (:taxonUuids) "
459 ;
460 Query<Object[]> query = getSession().createQuery(hql, Object[].class);
461 query.setParameter("classificationUuid", classificationUuid);
462 query.setParameterList("taxonUuids", taxonUuids);
463
464 Map<UUID, TreeIndex> result = new HashMap<>();
465 List<Object[]> list = query.list();
466 for (Object[] o : list){
467 result.put((UUID)o[0], TreeIndex.NewInstance((String)o[1]));
468 }
469 return result;
470 }
471
472 @Override
473 public Set<TreeIndex> getMarkedTreeIndexes(MarkerType markerType, Boolean flag){
474 String hql = " SELECT tn.treeIndex "
475 + " FROM Taxon t "
476 + " JOIN t.taxonNodes tn "
477 + " JOIN t.markers m "
478 + " WHERE (1=1)"
479 + " AND m.markerType = :markerType "
480 ;
481 if (flag != null){
482 hql += " AND m.flag = :flag ";
483
484 }
485
486 Query<String> query = getSession().createQuery(hql, String.class);
487 if (flag != null){
488 query.setParameter("flag", flag);
489 }
490 query.setParameter("markerType", markerType);
491
492 Set<TreeIndex> result = new HashSet<>();
493
494 List<String> list = query.list();
495 for (String o : list){
496 result.add(TreeIndex.NewInstance(o));
497 }
498 return result;
499 }
500
501 @Override
502 public Map<UUID, UUID> getTaxonNodeUuidByTaxonUuid(UUID classificationUuid, List<UUID> taxonUuids) {
503 String hql = " SELECT t.uuid, tn.uuid "
504 + " FROM Taxon t JOIN t.taxonNodes tn "
505 + " WHERE (1=1)"
506 + " AND tn.classification.uuid = :classificationUuid "
507 + " AND t.uuid IN (:taxonUuids) "
508 ;
509 Query<Object[]> query = getSession().createQuery(hql, Object[].class);
510 query.setParameter("classificationUuid", classificationUuid);
511 query.setParameterList("taxonUuids", taxonUuids);
512
513 Map<UUID, UUID> result = new HashMap<>();
514 List<Object[]> list = query.list();
515 for (Object[] o : list){
516 result.put((UUID)o[0], (UUID)o[1]);
517 }
518 return result;
519 }
520 }