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
.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
;
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
){
217 Classification classification
= dao
.load(classificationUuid
);
218 Taxon taxon
= (Taxon
) taxonDao
.load(taxonUuid
);
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
226 public TaxonNode
getTaxonNodeByUuid(UUID uuid
) {
227 return taxonNodeDao
.findByUuid(uuid
);
231 public ITaxonTreeNode
getTreeNodeByUuid(UUID uuid
){
232 ITaxonTreeNode treeNode
= taxonNodeDao
.findByUuid(uuid
);
233 if(treeNode
== null){
234 treeNode
= dao
.findByUuid(uuid
);
241 public List
<Classification
> listClassifications(Integer limit
, Integer start
, List
<OrderHint
> orderHints
, List
<String
> propertyPaths
) {
242 return dao
.list(limit
, start
, orderHints
, propertyPaths
);
246 public UUID
removeTaxonNode(TaxonNode taxonNode
) {
247 return taxonNodeDao
.delete(taxonNode
);
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
);
259 public UUID
saveTaxonNode(TaxonNode taxonNode
) {
260 return taxonNodeDao
.save(taxonNode
);
264 public Map
<UUID
, TaxonNode
> saveTaxonNodeAll(
265 Collection
<TaxonNode
> taxonNodeCollection
) {
266 return taxonNodeDao
.saveAll(taxonNodeCollection
);
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
);
280 public List
<TaxonNode
> getAllNodes(){
281 return taxonNodeDao
.list(null,null);
285 public List
<UuidAndTitleCache
<TaxonNode
>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification
) {
286 return taxonDao
.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification
);
290 public List
<UuidAndTitleCache
<Classification
>> getUuidAndTitleCache() {
291 return dao
.getUuidAndTitleCache();
295 public Map
<UUID
, List
<MediaRepresentation
>> getAllMediaForChildNodes(
296 TaxonNode taxonNode
, List
<String
> propertyPaths
, int size
,
297 int height
, int widthOrDuration
, String
[] mimeTypes
) {
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
>();
303 //add all media of the children to the result map
304 if (taxonNode
!= null){
306 List
<TaxonNode
> nodes
= new ArrayList
<TaxonNode
>();
308 nodes
.add(loadTaxonNode(taxonNode
, propertyPaths
));
309 nodes
.addAll(loadChildNodesOfTaxonNode(taxonNode
, propertyPaths
));
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
);
319 //find the best matching representation
320 mediaRepresentations
.add(MediaUtils
.findBestMatchingRepresentation(media
,null, size
, height
, widthOrDuration
, mimeTypes
));
325 result
.put(taxon
.getUuid(), mediaRepresentations
);
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
);
340 return getAllMediaForChildNodes(node
, propertyPaths
, size
, height
, widthOrDuration
, mimeTypes
);
344 @Transactional(readOnly
= false)
345 public void updateTitleCache(Class
<?
extends Classification
> clazz
, Integer stepSize
, IIdentifiableEntityCacheStrategy
<Classification
> cacheStrategy
, IProgressMonitor monitor
) {
347 clazz
= Classification
.class;
349 super.updateTitleCacheImpl(clazz
, stepSize
, cacheStrategy
, monitor
);
354 * @param allNodesOfClassification
355 * @return null - if allNodesOfClassification is empty <br>
358 private Map
<String
, List
<TaxonNode
>> getSortedGenusList(Collection
<TaxonNode
> allNodesOfClassification
){
360 if(allNodesOfClassification
== null || allNodesOfClassification
.isEmpty()){
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+");
374 genusOrUninomial
= s
;
378 //if node has children
380 //retrieve list from map if not create List
381 if(sortedGenusMap
.containsKey(genusOrUninomial
)){
382 List
<TaxonNode
> list
= sortedGenusMap
.get(genusOrUninomial
);
384 sortedGenusMap
.put(genusOrUninomial
, list
);
386 //create List for genus
387 List
<TaxonNode
> list
= new ArrayList
<TaxonNode
>();
389 sortedGenusMap
.put(genusOrUninomial
, list
);
392 return sortedGenusMap
;
397 * creates new Classification and parent TaxonNodes at genus level
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
405 @SuppressWarnings({ "rawtypes", "unchecked" })
406 @Transactional(readOnly
= false)
408 public Classification
createHierarchyInClassification(Classification classification
, CreateHierarchyForClassificationConfigurator configurator
){
409 classification
= dao
.findByUuid(classification
.getUuid());
410 Map
<String
, List
<TaxonNode
>> map
= getSortedGenusList(classification
.getAllNodes());
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());
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
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
);
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
);
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);
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
468 if(!tn
.isTopmostNode()){
469 continue; //skip to next taxonNode
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
);
486 dao
.saveOrUpdate(newClassification
);
487 return newClassification
;
492 * recursive method to get all childnodes of taxonNode in classification.
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
499 private List
<TaxonNode
> copyAllChildrenToTaxonNode(TaxonNode copyFromNode
, TaxonNode copyToNode
) {
500 List
<TaxonNode
> childNodes
;
501 if(!copyFromNode
.hasChildNodes()){
504 childNodes
= copyFromNode
.getChildNodes();
506 for(TaxonNode childNode
:childNodes
){
507 TaxonNode clone
= (TaxonNode
) childNode
.clone();
508 if(childNode
.hasChildNodes()){
509 copyAllChildrenToTaxonNode(childNode
, clone
);
511 //FIXME: citation from node instead of classification
512 // copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
513 copyToNode
.addChildNode(clone
, clone
.getReference(), clone
.getMicroReference());