#5056 Refactored 'save' call to return saved entity
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / ClassificationServiceImpl.java
1 // $Id$
2 /**
3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10
11 package eu.etaxonomy.cdm.api.service;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.TreeMap;
21 import java.util.UUID;
22
23 import org.apache.commons.collections.CollectionUtils;
24 import org.apache.log4j.Logger;
25 import org.springframework.beans.factory.annotation.Autowired;
26 import org.springframework.stereotype.Service;
27 import org.springframework.transaction.annotation.Transactional;
28
29 import eu.etaxonomy.cdm.api.service.config.CreateHierarchyForClassificationConfigurator;
30 import eu.etaxonomy.cdm.api.service.pager.Pager;
31 import eu.etaxonomy.cdm.api.service.pager.PagerUtils;
32 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
33 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
34 import eu.etaxonomy.cdm.model.common.CdmBase;
35 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
36 import eu.etaxonomy.cdm.model.description.TaxonDescription;
37 import eu.etaxonomy.cdm.model.media.Media;
38 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
39 import eu.etaxonomy.cdm.model.media.MediaUtils;
40 import eu.etaxonomy.cdm.model.name.NonViralName;
41 import eu.etaxonomy.cdm.model.name.Rank;
42 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
43 import eu.etaxonomy.cdm.model.taxon.Classification;
44 import eu.etaxonomy.cdm.model.taxon.ITaxonNodeComparator;
45 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
46 import eu.etaxonomy.cdm.model.taxon.Taxon;
47 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
48 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
49 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
50 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
51 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
52 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
53 import eu.etaxonomy.cdm.persistence.query.OrderHint;
54 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
55 import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
56
57 /**
58 * @author n.hoffmann
59 * @created Sep 21, 2009
60 */
61 @Service
62 @Transactional(readOnly = true)
63 public class ClassificationServiceImpl extends IdentifiableServiceBase<Classification, IClassificationDao>
64 implements IClassificationService {
65 private static final Logger logger = Logger.getLogger(ClassificationServiceImpl.class);
66
67 @Autowired
68 private ITaxonNodeDao taxonNodeDao;
69
70 @Autowired
71 private ITaxonDao taxonDao;
72
73 @Autowired
74 private IBeanInitializer defaultBeanInitializer;
75
76 @Override
77 @Autowired
78 protected void setDao(IClassificationDao dao) {
79 this.dao = dao;
80 }
81
82 private Comparator<? super TaxonNode> taxonNodeComparator;
83
84 @Autowired
85 public void setTaxonNodeComparator(ITaxonNodeComparator<? super TaxonNode> taxonNodeComparator){
86 this.taxonNodeComparator = (Comparator<? super TaxonNode>) taxonNodeComparator;
87 }
88
89 @Override
90 public TaxonNode loadTaxonNodeByTaxon(Taxon taxon, UUID classificationUuid, List<String> propertyPaths){
91 Classification tree = dao.load(classificationUuid);
92 TaxonNode node = tree.getNode(taxon);
93
94 return loadTaxonNode(node.getUuid(), propertyPaths);
95 }
96
97 @Override
98 @Deprecated // use loadTaxonNode(UUID, List<String>) instead
99 public TaxonNode loadTaxonNode(TaxonNode taxonNode, List<String> propertyPaths){
100 return taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
101 }
102
103 public TaxonNode loadTaxonNode(UUID taxonNodeUuid, List<String> propertyPaths){
104 return taxonNodeDao.load(taxonNodeUuid, propertyPaths);
105 }
106
107 @Override
108 @Deprecated
109 public List<TaxonNode> loadRankSpecificRootNodes(Classification classification, Rank rank, Integer limit, Integer start, List<String> propertyPaths){
110
111 List<TaxonNode> rootNodes = dao.listRankSpecificRootNodes(classification, rank, limit , start, propertyPaths);
112
113 //sort nodes by TaxonName
114 Collections.sort(rootNodes, taxonNodeComparator);
115
116 // initialize all nodes
117 defaultBeanInitializer.initializeAll(rootNodes, propertyPaths);
118
119 return rootNodes;
120 }
121
122 @Override
123 public List<TaxonNode> listRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
124 Integer pageIndex, List<String> propertyPaths) {
125 return pageRankSpecificRootNodes(classification, rank, pageSize, pageIndex, propertyPaths).getRecords();
126 }
127
128 @Override
129 public Pager<TaxonNode> pageRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
130 Integer pageIndex, List<String> propertyPaths) {
131 Long numberOfResults = dao.countRankSpecificRootNodes(classification, rank);
132
133 List<TaxonNode> results = new ArrayList<TaxonNode>();
134 if (numberOfResults > 0) { // no point checking again
135
136 results = dao.listRankSpecificRootNodes(classification, rank, PagerUtils.limitFor(pageSize),
137 PagerUtils.startFor(pageSize, pageIndex), propertyPaths);
138 }
139
140 Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
141 return new DefaultPagerImpl<TaxonNode>(pageIndex, numberOfResults.intValue(), pageSize, results);
142
143 }
144
145 /**
146 * @implements {@link IClassificationService#loadTreeBranch(TaxonNode, Rank, List)
147 * @see eu.etaxonomy.cdm.api.service.ITaxonService#loadTreeBranchTo(eu.etaxonomy.cdm.model.taxon.TaxonNode, eu.etaxonomy.cdm.model.name.Rank, java.util.List)
148 * FIXME Candidate for harmonization
149 * move to classification service
150 */
151 @Override
152 public List<TaxonNode> loadTreeBranch(TaxonNode taxonNode, Rank baseRank, List<String> propertyPaths){
153
154 TaxonNode thisNode = taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
155 List<TaxonNode> pathToRoot = new ArrayList<TaxonNode>();
156 pathToRoot.add(thisNode);
157
158 while(!thisNode.isTopmostNode()){
159 //TODO why do we need to deproxy here?
160 // without this thisNode.getParent() will return NULL in
161 // some cases (environment dependend?) even if the parent exits
162 TaxonNode parentNode = CdmBase.deproxy(thisNode, TaxonNode.class).getParent();
163
164 if(parentNode == null){
165 throw new NullPointerException("taxonNode " + thisNode + " must have a parent since it is not top most");
166 }
167 if(parentNode.getTaxon() == null){
168 throw new NullPointerException("The taxon associated with taxonNode " + parentNode + " is NULL");
169 }
170 if(parentNode.getTaxon().getName() == null){
171 throw new NullPointerException("The name of the taxon associated with taxonNode " + parentNode + " is NULL");
172 }
173
174 Rank parentNodeRank = parentNode.getTaxon().getName() == null ? null : parentNode.getTaxon().getName().getRank();
175 // stop if the next parent is higher than the baseRank
176 if(baseRank != null && parentNodeRank != null && baseRank.isLower(parentNodeRank)){
177 break;
178 }
179
180 pathToRoot.add(parentNode);
181 thisNode = parentNode;
182 }
183
184 // initialize and invert order of nodes in list
185 defaultBeanInitializer.initializeAll(pathToRoot, propertyPaths);
186 Collections.reverse(pathToRoot);
187
188 return pathToRoot;
189 }
190
191 @Override
192 public List<TaxonNode> loadTreeBranchToTaxon(Taxon taxon, Classification classification, Rank baseRank, List<String> propertyPaths){
193 Classification tree = dao.load(classification.getUuid());
194 taxon = (Taxon) taxonDao.load(taxon.getUuid());
195 TaxonNode node = tree.getNode(taxon);
196 if(node == null){
197 logger.warn("The specified taxon is not found in the given tree.");
198 return null;
199 }
200 return loadTreeBranch(node, baseRank, propertyPaths);
201 }
202
203
204 @Override
205 public List<TaxonNode> loadChildNodesOfTaxonNode(TaxonNode taxonNode,
206 List<String> propertyPaths) {
207 taxonNode = taxonNodeDao.load(taxonNode.getUuid());
208 List<TaxonNode> childNodes = new ArrayList<TaxonNode>(taxonNode.getChildNodes());
209 defaultBeanInitializer.initializeAll(childNodes, propertyPaths);
210 Collections.sort(childNodes, taxonNodeComparator);
211 return childNodes;
212 }
213
214 @Override
215 public List<TaxonNode> listChildNodesOfTaxon(UUID taxonUuid, UUID classificationUuid, Integer pageSize,
216 Integer pageIndex, List<String> propertyPaths){
217
218 Classification classification = dao.load(classificationUuid);
219 Taxon taxon = (Taxon) taxonDao.load(taxonUuid);
220
221 List<TaxonNode> results = dao.listChildrenOf(taxon, classification, pageSize, pageIndex, propertyPaths);
222 Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
223 return results;
224 }
225
226 @Override
227 public TaxonNode getTaxonNodeByUuid(UUID uuid) {
228 return taxonNodeDao.findByUuid(uuid);
229 }
230
231 @Override
232 public ITaxonTreeNode getTreeNodeByUuid(UUID uuid){
233 ITaxonTreeNode treeNode = taxonNodeDao.findByUuid(uuid);
234 if(treeNode == null){
235 treeNode = dao.findByUuid(uuid);
236 }
237
238 return treeNode;
239 }
240
241 @Override
242 public List<Classification> listClassifications(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
243 return dao.list(limit, start, orderHints, propertyPaths);
244 }
245
246 @Override
247 public UUID removeTaxonNode(TaxonNode taxonNode) {
248 return taxonNodeDao.delete(taxonNode);
249 }
250 @Override
251 public UUID removeTreeNode(ITaxonTreeNode treeNode) {
252 if(treeNode instanceof Classification){
253 return dao.delete((Classification) treeNode);
254 }else if(treeNode instanceof TaxonNode){
255 return taxonNodeDao.delete((TaxonNode)treeNode);
256 }
257 return null;
258 }
259 @Override
260 public UUID saveTaxonNode(TaxonNode taxonNode) {
261 return taxonNodeDao.save(taxonNode).getUuid();
262 }
263
264 @Override
265 public Map<UUID, TaxonNode> saveTaxonNodeAll(
266 Collection<TaxonNode> taxonNodeCollection) {
267 return taxonNodeDao.saveAll(taxonNodeCollection);
268 }
269
270 @Override
271 public UUID saveTreeNode(ITaxonTreeNode treeNode) {
272 if(treeNode instanceof Classification){
273 return dao.save((Classification) treeNode).getUuid();
274 }else if(treeNode instanceof TaxonNode){
275 return taxonNodeDao.save((TaxonNode)treeNode).getUuid();
276 }
277 return null;
278 }
279
280 @Override
281 public List<TaxonNode> getAllNodes(){
282 return taxonNodeDao.list(null,null);
283 }
284
285 @Override
286 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification) {
287 return taxonDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification);
288 }
289
290 @Override
291 public List<UuidAndTitleCache<Classification>> getUuidAndTitleCache() {
292 return dao.getUuidAndTitleCache();
293 }
294
295 @Override
296 public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(
297 TaxonNode taxonNode, List<String> propertyPaths, int size,
298 int height, int widthOrDuration, String[] mimeTypes) {
299
300 TreeMap<UUID, List<MediaRepresentation>> result = new TreeMap<UUID, List<MediaRepresentation>>();
301 List<Media> taxonMedia = new ArrayList<Media>();
302 List<MediaRepresentation> mediaRepresentations = new ArrayList<MediaRepresentation>();
303
304 //add all media of the children to the result map
305 if (taxonNode != null){
306
307 List<TaxonNode> nodes = new ArrayList<TaxonNode>();
308
309 nodes.add(loadTaxonNode(taxonNode, propertyPaths));
310 nodes.addAll(loadChildNodesOfTaxonNode(taxonNode, propertyPaths));
311
312 if (nodes != null){
313 for(TaxonNode node : nodes){
314 Taxon taxon = node.getTaxon();
315 for (TaxonDescription taxonDescription: taxon.getDescriptions()){
316 for (DescriptionElementBase descriptionElement: taxonDescription.getElements()){
317 for(Media media : descriptionElement.getMedia()){
318 taxonMedia.add(media);
319
320 //find the best matching representation
321 mediaRepresentations.add(MediaUtils.findBestMatchingRepresentation(media,null, size, height, widthOrDuration, mimeTypes));
322
323 }
324 }
325 }
326 result.put(taxon.getUuid(), mediaRepresentations);
327
328 }
329 }
330
331 }
332
333 return result;
334
335 }
336
337 @Override
338 public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(Taxon taxon, Classification taxTree, List<String> propertyPaths, int size, int height, int widthOrDuration, String[] mimeTypes){
339 TaxonNode node = taxTree.getNode(taxon);
340
341 return getAllMediaForChildNodes(node, propertyPaths, size, height, widthOrDuration, mimeTypes);
342 }
343
344 @Override
345 @Transactional(readOnly = false)
346 public void updateTitleCache(Class<? extends Classification> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Classification> cacheStrategy, IProgressMonitor monitor) {
347 if (clazz == null){
348 clazz = Classification.class;
349 }
350 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
351 }
352
353 /**
354 *
355 * @param allNodesOfClassification
356 * @return null - if allNodesOfClassification is empty <br>
357 */
358
359 private Map<String, List<TaxonNode>> getSortedGenusList(Collection<TaxonNode> allNodesOfClassification){
360
361 if(allNodesOfClassification == null || allNodesOfClassification.isEmpty()){
362 return null;
363 }
364 Map<String, List<TaxonNode>> sortedGenusMap = new HashMap<String, List<TaxonNode>>();
365 for(TaxonNode node:allNodesOfClassification){
366 final TaxonNode tn = node;
367 Taxon taxon = node.getTaxon();
368 NonViralName name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
369 String genusOrUninomial = name.getGenusOrUninomial();
370 //if rank unknown split string and take first word
371 if(genusOrUninomial == null){
372 String titleCache = taxon.getTitleCache();
373 String[] split = titleCache.split("\\s+");
374 for(String s:split){
375 genusOrUninomial = s;
376 break;
377 }
378 }
379 //if node has children
380
381 //retrieve list from map if not create List
382 if(sortedGenusMap.containsKey(genusOrUninomial)){
383 List<TaxonNode> list = sortedGenusMap.get(genusOrUninomial);
384 list.add(node);
385 sortedGenusMap.put(genusOrUninomial, list);
386 }else{
387 //create List for genus
388 List<TaxonNode> list = new ArrayList<TaxonNode>();
389 list.add(node);
390 sortedGenusMap.put(genusOrUninomial, list);
391 }
392 }
393 return sortedGenusMap;
394 }
395
396 /**
397 *
398 * creates new Classification and parent TaxonNodes at genus level
399 *
400 *
401 * @param map GenusMap which holds a name (Genus) and all the same Taxa as a list
402 * @param classification you want to improve the hierarchy (will not be modified)
403 * @param configurator to change certain settings, if null then standard settings will be taken
404 * @return new classification with parentNodes for each entry in the map
405 */
406 @SuppressWarnings({ "rawtypes", "unchecked" })
407 @Transactional(readOnly = false)
408 @Override
409 public UpdateResult createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator){
410 UpdateResult result = new UpdateResult();
411 classification = dao.findByUuid(classification.getUuid());
412 Map<String, List<TaxonNode>> map = getSortedGenusList(classification.getAllNodes());
413
414 final String APPENDIX = "repaired";
415 String titleCache = org.apache.commons.lang.StringUtils.isBlank(classification.getTitleCache()) ? " " : classification.getTitleCache() ;
416 //TODO classification clone???
417 Classification newClassification = Classification.NewInstance(titleCache +" "+ APPENDIX);
418 newClassification.setReference(classification.getReference());
419
420 for(Map.Entry<String, List<TaxonNode>> entry:map.entrySet()){
421 String genus = entry.getKey();
422 List<TaxonNode> listOfTaxonNodes = entry.getValue();
423 TaxonNode parentNode = null;
424 //Search for genus in list
425 for(TaxonNode tNode:listOfTaxonNodes){
426 //take that taxonNode as parent and remove from list with all it possible children
427 //FIXME NPE for name
428 TaxonNameBase name = tNode.getTaxon().getName();
429 NonViralName nonViralName = CdmBase.deproxy(name, NonViralName.class);
430 if(nonViralName.getNameCache().equalsIgnoreCase(genus)){
431 TaxonNode clone = (TaxonNode) tNode.clone();
432 if(!tNode.hasChildNodes()){
433 //FIXME remove classification
434 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
435 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
436 //remove taxonNode from list because just added to classification
437 result.addUpdatedObject(tNode);
438 listOfTaxonNodes.remove(tNode);
439 }else{
440 //get all childNodes
441 //save prior Hierarchy and remove them from the list
442 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tNode, clone, result);
443 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
444 //FIXME remove classification
445 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
446 //remove taxonNode from list because just added to classification
447 result.addUpdatedObject(tNode);
448 listOfTaxonNodes.remove(tNode);
449 if(copyAllChildrenToTaxonNode != null){
450 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
451 }
452 }
453 break;
454 }
455 }
456 if(parentNode == null){
457 //if no match found in list, create parentNode
458 NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
459 NonViralName nonViralName = parser.parseFullName(genus);
460 TaxonNameBase taxonNameBase = nonViralName;
461 //TODO Sec via configurator
462 Taxon taxon = Taxon.NewInstance(taxonNameBase, null);
463 parentNode = newClassification.addChildTaxon(taxon, 0, null, null);
464 result.addUpdatedObject(parentNode);
465 }
466 //iterate over the rest of the list
467 for(TaxonNode tn : listOfTaxonNodes){
468 //if TaxonNode has a parent and this is not the classification then skip it
469 //and add to new classification via the parentNode as children of it
470 //this should assures to keep the already existing hierarchy
471 //FIXME: Assert is not rootnode --> entrypoint is not classification in future but rather rootNode
472
473 if(!tn.isTopmostNode()){
474 continue; //skip to next taxonNode
475 }
476
477 TaxonNode clone = (TaxonNode) tn.clone();
478 //FIXME: citation from node
479 //TODO: addchildNode without citation and references
480 // TaxonNode taxonNode = parentNode.addChildNode(clone, classification.getCitation(), classification.getMicroReference());
481 TaxonNode taxonNode = parentNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
482 result.addUnChangedObject(clone);
483 if(tn.hasChildNodes()){
484 //save hierarchy in new classification
485 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tn, taxonNode, result);
486 if(copyAllChildrenToTaxonNode != null){
487 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
488 }
489 }
490 }
491 }
492 dao.saveOrUpdate(newClassification);
493 result.setCdmEntity(newClassification);
494 return result;
495 }
496
497 /**
498 *
499 * recursive method to get all childnodes of taxonNode in classification.
500 *
501 * @param classification just for References and Citation, can be null
502 * @param copyFromNode TaxonNode with Children
503 * @param copyToNode TaxonNode which will receive the children
504 * @return List of ChildNode which has been added. If node has no children returns null
505 */
506 private List<TaxonNode> copyAllChildrenToTaxonNode(TaxonNode copyFromNode, TaxonNode copyToNode, UpdateResult result) {
507 List<TaxonNode> childNodes;
508 if(!copyFromNode.hasChildNodes()){
509 return null;
510 }else{
511 childNodes = copyFromNode.getChildNodes();
512 }
513 for(TaxonNode childNode:childNodes){
514 TaxonNode clone = (TaxonNode) childNode.clone();
515 result.addUnChangedObject(clone);
516 if(childNode.hasChildNodes()){
517 copyAllChildrenToTaxonNode(childNode, clone, result);
518 }
519 //FIXME: citation from node instead of classification
520 // copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
521 copyToNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
522 }
523 return childNodes;
524 }
525
526 }