3 * Copyright (C) 2007 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
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.
11 package eu
.etaxonomy
.cdm
.api
.service
;
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
;
20 import java
.util
.TreeMap
;
21 import java
.util
.UUID
;
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
;
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
;
59 * @created Sep 21, 2009
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);
68 private ITaxonNodeDao taxonNodeDao
;
71 private ITaxonDao taxonDao
;
74 private IBeanInitializer defaultBeanInitializer
;
78 protected void setDao(IClassificationDao dao
) {
82 private Comparator
<?
super TaxonNode
> taxonNodeComparator
;
85 public void setTaxonNodeComparator(ITaxonNodeComparator
<?
super TaxonNode
> taxonNodeComparator
){
86 this.taxonNodeComparator
= (Comparator
<?
super TaxonNode
>) taxonNodeComparator
;
90 public TaxonNode
loadTaxonNodeByTaxon(Taxon taxon
, UUID classificationUuid
, List
<String
> propertyPaths
){
91 Classification tree
= dao
.load(classificationUuid
);
92 TaxonNode node
= tree
.getNode(taxon
);
94 return loadTaxonNode(node
.getUuid(), propertyPaths
);
98 @Deprecated // use loadTaxonNode(UUID, List<String>) instead
99 public TaxonNode
loadTaxonNode(TaxonNode taxonNode
, List
<String
> propertyPaths
){
100 return taxonNodeDao
.load(taxonNode
.getUuid(), propertyPaths
);
103 public TaxonNode
loadTaxonNode(UUID taxonNodeUuid
, List
<String
> propertyPaths
){
104 return taxonNodeDao
.load(taxonNodeUuid
, propertyPaths
);
109 public List
<TaxonNode
> loadRankSpecificRootNodes(Classification classification
, Rank rank
, Integer limit
, Integer start
, List
<String
> propertyPaths
){
111 List
<TaxonNode
> rootNodes
= dao
.listRankSpecificRootNodes(classification
, rank
, limit
, start
, propertyPaths
);
113 //sort nodes by TaxonName
114 Collections
.sort(rootNodes
, taxonNodeComparator
);
116 // initialize all nodes
117 defaultBeanInitializer
.initializeAll(rootNodes
, propertyPaths
);
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();
129 public Pager
<TaxonNode
> pageRankSpecificRootNodes(Classification classification
, Rank rank
, Integer pageSize
,
130 Integer pageIndex
, List
<String
> propertyPaths
) {
131 Long numberOfResults
= dao
.countRankSpecificRootNodes(classification
, rank
);
133 List
<TaxonNode
> results
= new ArrayList
<TaxonNode
>();
134 if (numberOfResults
> 0) { // no point checking again
136 results
= dao
.listRankSpecificRootNodes(classification
, rank
, PagerUtils
.limitFor(pageSize
),
137 PagerUtils
.startFor(pageSize
, pageIndex
), propertyPaths
);
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
);
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
152 public List
<TaxonNode
> loadTreeBranch(TaxonNode taxonNode
, Rank baseRank
, List
<String
> propertyPaths
){
154 TaxonNode thisNode
= taxonNodeDao
.load(taxonNode
.getUuid(), propertyPaths
);
155 List
<TaxonNode
> pathToRoot
= new ArrayList
<TaxonNode
>();
156 pathToRoot
.add(thisNode
);
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();
164 if(parentNode
== null){
165 throw new NullPointerException("taxonNode " + thisNode
+ " must have a parent since it is not top most");
167 if(parentNode
.getTaxon() == null){
168 throw new NullPointerException("The taxon associated with taxonNode " + parentNode
+ " is NULL");
170 if(parentNode
.getTaxon().getName() == null){
171 throw new NullPointerException("The name of the taxon associated with taxonNode " + parentNode
+ " is NULL");
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
)){
180 pathToRoot
.add(parentNode
);
181 thisNode
= parentNode
;
184 // initialize and invert order of nodes in list
185 defaultBeanInitializer
.initializeAll(pathToRoot
, propertyPaths
);
186 Collections
.reverse(pathToRoot
);
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
);
197 logger
.warn("The specified taxon is not found in the given tree.");
200 return loadTreeBranch(node
, baseRank
, propertyPaths
);
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
);
215 public List
<TaxonNode
> listChildNodesOfTaxon(UUID taxonUuid
, UUID classificationUuid
, Integer pageSize
,
216 Integer pageIndex
, List
<String
> propertyPaths
){
218 Classification classification
= dao
.load(classificationUuid
);
219 Taxon taxon
= (Taxon
) taxonDao
.load(taxonUuid
);
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
227 public TaxonNode
getTaxonNodeByUuid(UUID uuid
) {
228 return taxonNodeDao
.findByUuid(uuid
);
232 public ITaxonTreeNode
getTreeNodeByUuid(UUID uuid
){
233 ITaxonTreeNode treeNode
= taxonNodeDao
.findByUuid(uuid
);
234 if(treeNode
== null){
235 treeNode
= dao
.findByUuid(uuid
);
242 public List
<Classification
> listClassifications(Integer limit
, Integer start
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
243 return dao
.list(limit
, start
, orderHints
, propertyPaths
);
247 public UUID
removeTaxonNode(TaxonNode taxonNode
) {
248 return taxonNodeDao
.delete(taxonNode
);
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
);
260 public UUID
saveTaxonNode(TaxonNode taxonNode
) {
261 return taxonNodeDao
.save(taxonNode
).getUuid();
265 public Map
<UUID
, TaxonNode
> saveTaxonNodeAll(
266 Collection
<TaxonNode
> taxonNodeCollection
) {
267 return taxonNodeDao
.saveAll(taxonNodeCollection
);
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();
281 public List
<TaxonNode
> getAllNodes(){
282 return taxonNodeDao
.list(null,null);
286 public List
<UuidAndTitleCache
<TaxonNode
>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification
) {
287 return taxonDao
.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification
);
291 public List
<UuidAndTitleCache
<Classification
>> getUuidAndTitleCache() {
292 return dao
.getUuidAndTitleCache();
296 public Map
<UUID
, List
<MediaRepresentation
>> getAllMediaForChildNodes(
297 TaxonNode taxonNode
, List
<String
> propertyPaths
, int size
,
298 int height
, int widthOrDuration
, String
[] mimeTypes
) {
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
>();
304 //add all media of the children to the result map
305 if (taxonNode
!= null){
307 List
<TaxonNode
> nodes
= new ArrayList
<TaxonNode
>();
309 nodes
.add(loadTaxonNode(taxonNode
, propertyPaths
));
310 nodes
.addAll(loadChildNodesOfTaxonNode(taxonNode
, propertyPaths
));
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
);
320 //find the best matching representation
321 mediaRepresentations
.add(MediaUtils
.findBestMatchingRepresentation(media
,null, size
, height
, widthOrDuration
, mimeTypes
));
326 result
.put(taxon
.getUuid(), mediaRepresentations
);
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
);
341 return getAllMediaForChildNodes(node
, propertyPaths
, size
, height
, widthOrDuration
, mimeTypes
);
345 @Transactional(readOnly
= false)
346 public void updateTitleCache(Class
<?
extends Classification
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<Classification
> cacheStrategy
, IProgressMonitor monitor
) {
348 clazz
= Classification
.class;
350 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
355 * @param allNodesOfClassification
356 * @return null - if allNodesOfClassification is empty <br>
359 private Map
<String
, List
<TaxonNode
>> getSortedGenusList(Collection
<TaxonNode
> allNodesOfClassification
){
361 if(allNodesOfClassification
== null || allNodesOfClassification
.isEmpty()){
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+");
375 genusOrUninomial
= s
;
379 //if node has children
381 //retrieve list from map if not create List
382 if(sortedGenusMap
.containsKey(genusOrUninomial
)){
383 List
<TaxonNode
> list
= sortedGenusMap
.get(genusOrUninomial
);
385 sortedGenusMap
.put(genusOrUninomial
, list
);
387 //create List for genus
388 List
<TaxonNode
> list
= new ArrayList
<TaxonNode
>();
390 sortedGenusMap
.put(genusOrUninomial
, list
);
393 return sortedGenusMap
;
398 * creates new Classification and parent TaxonNodes at genus level
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
406 @SuppressWarnings({ "rawtypes", "unchecked" })
407 @Transactional(readOnly
= false)
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());
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());
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
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
);
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
);
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
);
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
473 if(!tn
.isTopmostNode()){
474 continue; //skip to next taxonNode
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
);
492 dao
.saveOrUpdate(newClassification
);
493 result
.setCdmEntity(newClassification
);
499 * recursive method to get all childnodes of taxonNode in classification.
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
506 private List
<TaxonNode
> copyAllChildrenToTaxonNode(TaxonNode copyFromNode
, TaxonNode copyToNode
, UpdateResult result
) {
507 List
<TaxonNode
> childNodes
;
508 if(!copyFromNode
.hasChildNodes()){
511 childNodes
= copyFromNode
.getChildNodes();
513 for(TaxonNode childNode
:childNodes
){
514 TaxonNode clone
= (TaxonNode
) childNode
.clone();
515 result
.addUnChangedObject(clone
);
516 if(childNode
.hasChildNodes()){
517 copyAllChildrenToTaxonNode(childNode
, clone
, result
);
519 //FIXME: citation from node instead of classification
520 // copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
521 copyToNode
.addChildNode(clone
, clone
.getReference(), clone
.getMicroReference());