created new service in classificationService
[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.common.UuidAndTitleCache;
36 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;
37 import eu.etaxonomy.cdm.model.description.TaxonDescription;
38 import eu.etaxonomy.cdm.model.media.Media;
39 import eu.etaxonomy.cdm.model.media.MediaRepresentation;
40 import eu.etaxonomy.cdm.model.media.MediaUtils;
41 import eu.etaxonomy.cdm.model.name.NonViralName;
42 import eu.etaxonomy.cdm.model.name.Rank;
43 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
44 import eu.etaxonomy.cdm.model.taxon.Classification;
45 import eu.etaxonomy.cdm.model.taxon.ITaxonNodeComparator;
46 import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode;
47 import eu.etaxonomy.cdm.model.taxon.Taxon;
48 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
49 import eu.etaxonomy.cdm.persistence.dao.initializer.IBeanInitializer;
50 import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao;
51 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao;
52 import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao;
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 Classification classification = dao.load(classificationUuid);
218 Taxon taxon = (Taxon) taxonDao.load(taxonUuid);
219
220 List<TaxonNode> results = dao.listChildrenOf(taxon, classification, pageSize, pageIndex, propertyPaths);
221 Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
222 return results;
223 }
224
225 @Override
226 public TaxonNode getTaxonNodeByUuid(UUID uuid) {
227 return taxonNodeDao.findByUuid(uuid);
228 }
229
230 @Override
231 public ITaxonTreeNode getTreeNodeByUuid(UUID uuid){
232 ITaxonTreeNode treeNode = taxonNodeDao.findByUuid(uuid);
233 if(treeNode == null){
234 treeNode = dao.findByUuid(uuid);
235 }
236
237 return treeNode;
238 }
239
240 @Override
241 public List<Classification> listClassifications(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
242 return dao.list(limit, start, orderHints, propertyPaths);
243 }
244
245 @Override
246 public UUID removeTaxonNode(TaxonNode taxonNode) {
247 return taxonNodeDao.delete(taxonNode);
248 }
249 @Override
250 public UUID removeTreeNode(ITaxonTreeNode treeNode) {
251 if(treeNode instanceof Classification){
252 return dao.delete((Classification) treeNode);
253 }else if(treeNode instanceof TaxonNode){
254 return taxonNodeDao.delete((TaxonNode)treeNode);
255 }
256 return null;
257 }
258 @Override
259 public UUID saveTaxonNode(TaxonNode taxonNode) {
260 return taxonNodeDao.save(taxonNode);
261 }
262
263 @Override
264 public Map<UUID, TaxonNode> saveTaxonNodeAll(
265 Collection<TaxonNode> taxonNodeCollection) {
266 return taxonNodeDao.saveAll(taxonNodeCollection);
267 }
268
269 @Override
270 public UUID saveTreeNode(ITaxonTreeNode treeNode) {
271 if(treeNode instanceof Classification){
272 return dao.save((Classification) treeNode);
273 }else if(treeNode instanceof TaxonNode){
274 return taxonNodeDao.save((TaxonNode)treeNode);
275 }
276 return null;
277 }
278
279 @Override
280 public List<TaxonNode> getAllNodes(){
281 return taxonNodeDao.list(null,null);
282 }
283
284 @Override
285 public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification) {
286 return taxonDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification);
287 }
288
289 @Override
290 public List<UuidAndTitleCache<Classification>> getUuidAndTitleCache() {
291 return dao.getUuidAndTitleCache();
292 }
293
294 @Override
295 public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(
296 TaxonNode taxonNode, List<String> propertyPaths, int size,
297 int height, int widthOrDuration, String[] mimeTypes) {
298
299 TreeMap<UUID, List<MediaRepresentation>> result = new TreeMap<UUID, List<MediaRepresentation>>();
300 List<Media> taxonMedia = new ArrayList<Media>();
301 List<MediaRepresentation> mediaRepresentations = new ArrayList<MediaRepresentation>();
302
303 //add all media of the children to the result map
304 if (taxonNode != null){
305
306 List<TaxonNode> nodes = new ArrayList<TaxonNode>();
307
308 nodes.add(loadTaxonNode(taxonNode, propertyPaths));
309 nodes.addAll(loadChildNodesOfTaxonNode(taxonNode, propertyPaths));
310
311 if (nodes != null){
312 for(TaxonNode node : nodes){
313 Taxon taxon = node.getTaxon();
314 for (TaxonDescription taxonDescription: taxon.getDescriptions()){
315 for (DescriptionElementBase descriptionElement: taxonDescription.getElements()){
316 for(Media media : descriptionElement.getMedia()){
317 taxonMedia.add(media);
318
319 //find the best matching representation
320 mediaRepresentations.add(MediaUtils.findBestMatchingRepresentation(media,null, size, height, widthOrDuration, mimeTypes));
321
322 }
323 }
324 }
325 result.put(taxon.getUuid(), mediaRepresentations);
326
327 }
328 }
329
330 }
331
332 return result;
333
334 }
335
336 @Override
337 public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(Taxon taxon, Classification taxTree, List<String> propertyPaths, int size, int height, int widthOrDuration, String[] mimeTypes){
338 TaxonNode node = taxTree.getNode(taxon);
339
340 return getAllMediaForChildNodes(node, propertyPaths, size, height, widthOrDuration, mimeTypes);
341 }
342
343 @Override
344 @Transactional(readOnly = false)
345 public void updateTitleCache(Class<? extends Classification> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Classification> cacheStrategy, IProgressMonitor monitor) {
346 if (clazz == null){
347 clazz = Classification.class;
348 }
349 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
350 }
351
352 /**
353 *
354 * @param allNodesOfClassification
355 * @return null - if allNodesOfClassification is empty <br>
356 */
357
358 private Map<String, List<TaxonNode>> getSortedGenusList(Collection<TaxonNode> allNodesOfClassification){
359
360 if(allNodesOfClassification == null || allNodesOfClassification.isEmpty()){
361 return null;
362 }
363 Map<String, List<TaxonNode>> sortedGenusMap = new HashMap<String, List<TaxonNode>>();
364 for(TaxonNode node:allNodesOfClassification){
365 final TaxonNode tn = node;
366 Taxon taxon = node.getTaxon();
367 NonViralName name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
368 String genusOrUninomial = name.getGenusOrUninomial();
369 //if rank unknown split string and take first word
370 if(genusOrUninomial == null){
371 String titleCache = taxon.getTitleCache();
372 String[] split = titleCache.split("\\s+");
373 for(String s:split){
374 genusOrUninomial = s;
375 break;
376 }
377 }
378 //if node has children
379
380 //retrieve list from map if not create List
381 if(sortedGenusMap.containsKey(genusOrUninomial)){
382 List<TaxonNode> list = sortedGenusMap.get(genusOrUninomial);
383 list.add(node);
384 sortedGenusMap.put(genusOrUninomial, list);
385 }else{
386 //create List for genus
387 List<TaxonNode> list = new ArrayList<TaxonNode>();
388 list.add(node);
389 sortedGenusMap.put(genusOrUninomial, list);
390 }
391 }
392 return sortedGenusMap;
393 }
394
395 /**
396 *
397 * creates new Classification and parent TaxonNodes at genus level
398 *
399 *
400 * @param map GenusMap which holds a name (Genus) and all the same Taxa as a list
401 * @param classification you want to improve the hierarchy (will not be modified)
402 * @param configurator to change certain settings, if null then standard settings will be taken
403 * @return new classification with parentNodes for each entry in the map
404 */
405 @SuppressWarnings({ "rawtypes", "unchecked" })
406 @Transactional(readOnly = false)
407 @Override
408 public Classification createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator){
409 classification = dao.findByUuid(classification.getUuid());
410 Map<String, List<TaxonNode>> map = getSortedGenusList(classification.getAllNodes());
411
412 final String APPENDIX = "repaired";
413 String titleCache = org.apache.commons.lang.StringUtils.isBlank(classification.getTitleCache()) ? " " : classification.getTitleCache() ;
414 //TODO classification clone???
415 Classification newClassification = Classification.NewInstance(titleCache +" "+ APPENDIX);
416 newClassification.setReference(classification.getReference());
417
418 for(Map.Entry<String, List<TaxonNode>> entry:map.entrySet()){
419 String genus = entry.getKey();
420 List<TaxonNode> listOfTaxonNodes = entry.getValue();
421 TaxonNode parentNode = null;
422 //Search for genus in list
423 for(TaxonNode tNode:listOfTaxonNodes){
424 //take that taxonNode as parent and remove from list with all it possible children
425 //FIXME NPE for name
426 TaxonNameBase name = tNode.getTaxon().getName();
427 NonViralName nonViralName = CdmBase.deproxy(name, NonViralName.class);
428 if(nonViralName.getNameCache().equalsIgnoreCase(genus)){
429 TaxonNode clone = (TaxonNode) tNode.clone();
430 if(!tNode.hasChildNodes()){
431 //FIXME remove classification
432 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
433 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
434 //remove taxonNode from list because just added to classification
435 listOfTaxonNodes.remove(tNode);
436 }else{
437 //get all childNodes
438 //save prior Hierarchy and remove them from the list
439 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tNode, clone);
440 // parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
441 //FIXME remove classification
442 parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
443 //remove taxonNode from list because just added to classification
444 listOfTaxonNodes.remove(tNode);
445 if(copyAllChildrenToTaxonNode != null){
446 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
447 }
448 }
449 break;
450 }
451 }
452 if(parentNode == null){
453 //if no match found in list, create parentNode
454 NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
455 NonViralName nonViralName = parser.parseFullName(genus);
456 TaxonNameBase taxonNameBase = (TaxonNameBase) nonViralName;
457 //TODO Sec via configurator
458 Taxon taxon = Taxon.NewInstance(taxonNameBase, null);
459 parentNode = newClassification.addChildTaxon(taxon, 0, null, null);
460 }
461 //iterate over the rest of the list
462 for(TaxonNode tn : listOfTaxonNodes){
463 //if TaxonNode has a parent and this is not the classification then skip it
464 //and add to new classification via the parentNode as children of it
465 //this should assures to keep the already existing hierarchy
466 //FIXME: Assert is not rootnode --> entrypoint is not classification in future but rather rootNode
467
468 if(!tn.isTopmostNode()){
469 continue; //skip to next taxonNode
470 }
471
472 TaxonNode clone = (TaxonNode) tn.clone();
473 //FIXME: citation from node
474 //TODO: addchildNode without citation and references
475 // TaxonNode taxonNode = parentNode.addChildNode(clone, classification.getCitation(), classification.getMicroReference());
476 TaxonNode taxonNode = parentNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
477 if(tn.hasChildNodes()){
478 //save hierarchy in new classification
479 List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tn, taxonNode);
480 if(copyAllChildrenToTaxonNode != null){
481 listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
482 }
483 }
484 }
485 }
486 dao.saveOrUpdate(newClassification);
487 return newClassification;
488 }
489
490 /**
491 *
492 * recursive method to get all childnodes of taxonNode in classification.
493 *
494 * @param classification just for References and Citation, can be null
495 * @param copyFromNode TaxonNode with Children
496 * @param copyToNode TaxonNode which will receive the children
497 * @return List of ChildNode which has been added. If node has no children returns null
498 */
499 private List<TaxonNode> copyAllChildrenToTaxonNode(TaxonNode copyFromNode, TaxonNode copyToNode) {
500 List<TaxonNode> childNodes;
501 if(!copyFromNode.hasChildNodes()){
502 return null;
503 }else{
504 childNodes = copyFromNode.getChildNodes();
505 }
506 for(TaxonNode childNode:childNodes){
507 TaxonNode clone = (TaxonNode) childNode.clone();
508 if(childNode.hasChildNodes()){
509 copyAllChildrenToTaxonNode(childNode, clone);
510 }
511 //FIXME: citation from node instead of classification
512 // copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
513 copyToNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
514 }
515 return childNodes;
516 }
517
518 }