Project

General

Profile

Download (22.1 KB) Statistics
| Branch: | Tag: | Revision:
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.AbstractPagerImpl;
33
import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
34
import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
35
import eu.etaxonomy.cdm.model.common.CdmBase;
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.dto.UuidAndTitleCache;
54
import eu.etaxonomy.cdm.persistence.query.OrderHint;
55
import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
56
import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;
57

    
58
/**
59
 * @author n.hoffmann
60
 * @created Sep 21, 2009
61
 */
62
@Service
63
@Transactional(readOnly = true)
64
public class ClassificationServiceImpl extends IdentifiableServiceBase<Classification, IClassificationDao>
65
    implements IClassificationService {
66
    private static final Logger logger = Logger.getLogger(ClassificationServiceImpl.class);
67

    
68
    @Autowired
69
    private ITaxonNodeDao taxonNodeDao;
70

    
71
    @Autowired
72
    private ITaxonDao taxonDao;
73

    
74
    @Autowired
75
    private IBeanInitializer defaultBeanInitializer;
76

    
77
    @Override
78
    @Autowired
79
    protected void setDao(IClassificationDao dao) {
80
        this.dao = dao;
81
    }
82

    
83
    private Comparator<? super TaxonNode> taxonNodeComparator;
84

    
85
    @Autowired
86
    public void setTaxonNodeComparator(ITaxonNodeComparator<? super TaxonNode> taxonNodeComparator){
87
        this.taxonNodeComparator = (Comparator<? super TaxonNode>) taxonNodeComparator;
88
    }
89

    
90
    @Override
91
    public TaxonNode loadTaxonNodeByTaxon(Taxon taxon, UUID classificationUuid, List<String> propertyPaths){
92
        Classification tree = dao.load(classificationUuid);
93
        TaxonNode node = tree.getNode(taxon);
94

    
95
        return loadTaxonNode(node.getUuid(), propertyPaths);
96
    }
97

    
98
    @Override
99
    @Deprecated // use loadTaxonNode(UUID, List<String>) instead
100
    public TaxonNode loadTaxonNode(TaxonNode taxonNode, List<String> propertyPaths){
101
        return taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
102
    }
103

    
104
    public TaxonNode loadTaxonNode(UUID taxonNodeUuid, List<String> propertyPaths){
105
        return taxonNodeDao.load(taxonNodeUuid, propertyPaths);
106
    }
107

    
108
    @Override
109
    public List<TaxonNode> listRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
110
            Integer pageIndex, List<String> propertyPaths) {
111
        return pageRankSpecificRootNodes(classification, rank, pageSize, pageIndex, propertyPaths).getRecords();
112
    }
113

    
114
    @Override
115
    public Pager<TaxonNode> pageRankSpecificRootNodes(Classification classification, Rank rank, Integer pageSize,
116
            Integer pageIndex, List<String> propertyPaths) {
117
        long[] numberOfResults = dao.countRankSpecificRootNodes(classification, rank);
118
        long totalNumberOfResults = numberOfResults[0] + (numberOfResults.length > 1 ? numberOfResults[1] : 0);
119

    
120
        List<TaxonNode> results = new ArrayList<TaxonNode>();
121

    
122
        if (AbstractPagerImpl.hasResultsInRange(totalNumberOfResults, pageIndex, pageSize)) { // no point checking again
123
            Integer limit = PagerUtils.limitFor(pageSize);
124
            Integer start = PagerUtils.startFor(pageSize, pageIndex);
125

    
126
            Integer remainingLimit = limit;
127
            int[] queryIndexes = rank == null ? new int[]{0} : new int[]{0,1};
128

    
129
            for(int queryIndex: queryIndexes) {
130
                if(start != null && start > numberOfResults[queryIndex]) {
131
                    // start in next query with new start value
132
                    start = start - (int)numberOfResults[queryIndex];
133
                    continue;
134
                }
135

    
136
                List<TaxonNode> perQueryResults = dao.listRankSpecificRootNodes(classification, rank, remainingLimit, start, propertyPaths, queryIndex);
137
                results.addAll(perQueryResults);
138
                if(remainingLimit != null ){
139
                    remainingLimit = remainingLimit - results.size();
140
                    if(remainingLimit <= 0) {
141
                        // no need to run further queries if first query returned enough items!
142
                        break;
143
                    }
144
                    // start at with fist item of next query to fetch the remaining items
145
                    start = 0;
146
                }
147
            }
148
        }
149

    
150
        Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
151
        return new DefaultPagerImpl<TaxonNode>(pageIndex, (int) totalNumberOfResults, pageSize, results);
152

    
153
    }
154

    
155
    /**
156
     * @implements {@link IClassificationService#loadTreeBranch(TaxonNode, Rank, List)
157
     * @see eu.etaxonomy.cdm.api.service.ITaxonService#loadTreeBranchTo(eu.etaxonomy.cdm.model.taxon.TaxonNode, eu.etaxonomy.cdm.model.name.Rank, java.util.List)
158
     * FIXME Candidate for harmonization
159
     * move to classification service
160
     */
161
    @Override
162
    public List<TaxonNode> loadTreeBranch(TaxonNode taxonNode, Rank baseRank, List<String> propertyPaths){
163

    
164
        TaxonNode thisNode = taxonNodeDao.load(taxonNode.getUuid(), propertyPaths);
165
        List<TaxonNode> pathToRoot = new ArrayList<TaxonNode>();
166
        pathToRoot.add(thisNode);
167

    
168
        while(!thisNode.isTopmostNode()){
169
            //TODO why do we need to deproxy here?
170
            //     without this thisNode.getParent() will return NULL in
171
            //     some cases (environment dependend?) even if the parent exits
172
            TaxonNode parentNode = CdmBase.deproxy(thisNode, TaxonNode.class).getParent();
173

    
174
            if(parentNode == null){
175
                throw new NullPointerException("taxonNode " + thisNode + " must have a parent since it is not top most");
176
            }
177
            if(parentNode.getTaxon() == null){
178
                throw new NullPointerException("The taxon associated with taxonNode " + parentNode + " is NULL");
179
            }
180
            if(parentNode.getTaxon().getName() == null){
181
                throw new NullPointerException("The name of the taxon associated with taxonNode " + parentNode + " is NULL");
182
            }
183

    
184
            Rank parentNodeRank = parentNode.getTaxon().getName() == null ? null : parentNode.getTaxon().getName().getRank();
185
            // stop if the next parent is higher than the baseRank
186
            if(baseRank != null && parentNodeRank != null && baseRank.isLower(parentNodeRank)){
187
                break;
188
            }
189

    
190
            pathToRoot.add(parentNode);
191
            thisNode = parentNode;
192
        }
193

    
194
        // initialize and invert order of nodes in list
195
        defaultBeanInitializer.initializeAll(pathToRoot, propertyPaths);
196
        Collections.reverse(pathToRoot);
197

    
198
        return pathToRoot;
199
    }
200

    
201
    @Override
202
    public List<TaxonNode> loadTreeBranchToTaxon(Taxon taxon, Classification classification, Rank baseRank, List<String> propertyPaths){
203
        Classification tree = dao.load(classification.getUuid());
204
        taxon = (Taxon) taxonDao.load(taxon.getUuid());
205
        TaxonNode node = tree.getNode(taxon);
206
        if(node == null){
207
            logger.warn("The specified taxon is not found in the given tree.");
208
            return null;
209
        }
210
        return loadTreeBranch(node, baseRank, propertyPaths);
211
    }
212

    
213

    
214
    @Override
215
    public List<TaxonNode> loadChildNodesOfTaxonNode(TaxonNode taxonNode,
216
            List<String> propertyPaths) {
217
        taxonNode = taxonNodeDao.load(taxonNode.getUuid());
218
        List<TaxonNode> childNodes = new ArrayList<TaxonNode>(taxonNode.getChildNodes());
219
        defaultBeanInitializer.initializeAll(childNodes, propertyPaths);
220
        Collections.sort(childNodes, taxonNodeComparator);
221
        return childNodes;
222
    }
223

    
224
    @Override
225
    public List<TaxonNode> listChildNodesOfTaxon(UUID taxonUuid, UUID classificationUuid, Integer pageSize,
226
            Integer pageIndex, List<String> propertyPaths){
227

    
228
        Classification classification = dao.load(classificationUuid);
229
        Taxon taxon = (Taxon) taxonDao.load(taxonUuid);
230

    
231
        List<TaxonNode> results = dao.listChildrenOf(taxon, classification, pageSize, pageIndex, propertyPaths);
232
        Collections.sort(results, taxonNodeComparator); // FIXME this is only a HACK, order during the hibernate query in the dao
233
        return results;
234
    }
235

    
236
    @Override
237
    public TaxonNode getTaxonNodeByUuid(UUID uuid) {
238
        return taxonNodeDao.findByUuid(uuid);
239
    }
240

    
241
    @Override
242
    public ITaxonTreeNode getTreeNodeByUuid(UUID uuid){
243
        ITaxonTreeNode treeNode = taxonNodeDao.findByUuid(uuid);
244
        if(treeNode == null){
245
            treeNode = dao.findByUuid(uuid);
246
        }
247

    
248
        return treeNode;
249
    }
250

    
251
    @Override
252
    public List<Classification> listClassifications(Integer limit, Integer start, List<OrderHint> orderHints, List<String> propertyPaths) {
253
        return dao.list(limit, start, orderHints, propertyPaths);
254
    }
255

    
256
    @Override
257
    public UUID removeTaxonNode(TaxonNode taxonNode) {
258
        return taxonNodeDao.delete(taxonNode);
259
    }
260
    @Override
261
    public UUID removeTreeNode(ITaxonTreeNode treeNode) {
262
        if(treeNode instanceof Classification){
263
            return dao.delete((Classification) treeNode);
264
        }else if(treeNode instanceof TaxonNode){
265
            return taxonNodeDao.delete((TaxonNode)treeNode);
266
        }
267
        return null;
268
    }
269
    @Override
270
    public UUID saveTaxonNode(TaxonNode taxonNode) {
271
        return taxonNodeDao.save(taxonNode).getUuid();
272
    }
273

    
274
    @Override
275
    public Map<UUID, TaxonNode> saveTaxonNodeAll(
276
            Collection<TaxonNode> taxonNodeCollection) {
277
        return taxonNodeDao.saveAll(taxonNodeCollection);
278
    }
279

    
280
    @Override
281
    public UUID saveTreeNode(ITaxonTreeNode treeNode) {
282
        if(treeNode instanceof Classification){
283
            return dao.save((Classification) treeNode).getUuid();
284
        }else if(treeNode instanceof TaxonNode){
285
            return taxonNodeDao.save((TaxonNode)treeNode).getUuid();
286
        }
287
        return null;
288
    }
289

    
290
    @Override
291
    public List<TaxonNode> getAllNodes(){
292
        return taxonNodeDao.list(null,null);
293
    }
294

    
295
    @Override
296
    public List<UuidAndTitleCache<TaxonNode>> getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(Classification classification) {
297
        return taxonDao.getTaxonNodeUuidAndTitleCacheOfAcceptedTaxaByClassification(classification);
298
    }
299

    
300
    @Override
301
    public List<UuidAndTitleCache<Classification>> getUuidAndTitleCache() {
302
        return dao.getUuidAndTitleCache();
303
    }
304

    
305
    @Override
306
    public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(
307
            TaxonNode taxonNode, List<String> propertyPaths, int size,
308
            int height, int widthOrDuration, String[] mimeTypes) {
309

    
310
        TreeMap<UUID, List<MediaRepresentation>> result = new TreeMap<UUID, List<MediaRepresentation>>();
311
        List<Media> taxonMedia = new ArrayList<Media>();
312
        List<MediaRepresentation> mediaRepresentations = new ArrayList<MediaRepresentation>();
313

    
314
        //add all media of the children to the result map
315
        if (taxonNode != null){
316

    
317
            List<TaxonNode> nodes = new ArrayList<TaxonNode>();
318

    
319
            nodes.add(loadTaxonNode(taxonNode, propertyPaths));
320
            nodes.addAll(loadChildNodesOfTaxonNode(taxonNode, propertyPaths));
321

    
322
            if (nodes != null){
323
                for(TaxonNode node : nodes){
324
                    Taxon taxon = node.getTaxon();
325
                    for (TaxonDescription taxonDescription: taxon.getDescriptions()){
326
                        for (DescriptionElementBase descriptionElement: taxonDescription.getElements()){
327
                            for(Media media : descriptionElement.getMedia()){
328
                                taxonMedia.add(media);
329

    
330
                                //find the best matching representation
331
                                mediaRepresentations.add(MediaUtils.findBestMatchingRepresentation(media,null, size, height, widthOrDuration, mimeTypes));
332

    
333
                            }
334
                        }
335
                    }
336
                    result.put(taxon.getUuid(), mediaRepresentations);
337

    
338
                }
339
            }
340

    
341
        }
342

    
343
        return result;
344

    
345
    }
346

    
347
    @Override
348
    public Map<UUID, List<MediaRepresentation>> getAllMediaForChildNodes(Taxon taxon, Classification taxTree, List<String> propertyPaths, int size, int height, int widthOrDuration, String[] mimeTypes){
349
        TaxonNode node = taxTree.getNode(taxon);
350

    
351
        return getAllMediaForChildNodes(node, propertyPaths, size, height, widthOrDuration, mimeTypes);
352
    }
353

    
354
    @Override
355
    @Transactional(readOnly = false)
356
    public void updateTitleCache(Class<? extends Classification> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<Classification> cacheStrategy, IProgressMonitor monitor) {
357
        if (clazz == null){
358
            clazz = Classification.class;
359
        }
360
        super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
361
    }
362

    
363
    /**
364
     *
365
     * @param allNodesOfClassification
366
     * @return null - if  allNodesOfClassification is empty <br>
367
     */
368

    
369
    private Map<String, List<TaxonNode>> getSortedGenusList(Collection<TaxonNode> allNodesOfClassification){
370

    
371
    	if(allNodesOfClassification == null || allNodesOfClassification.isEmpty()){
372
    		return null;
373
    	}
374
    	Map<String, List<TaxonNode>> sortedGenusMap = new HashMap<String, List<TaxonNode>>();
375
    	for(TaxonNode node:allNodesOfClassification){
376
    		final TaxonNode tn = node;
377
    		Taxon taxon = node.getTaxon();
378
    		NonViralName name = CdmBase.deproxy(taxon.getName(), NonViralName.class);
379
    		String genusOrUninomial = name.getGenusOrUninomial();
380
    		//if rank unknown split string and take first word
381
    		if(genusOrUninomial == null){
382
    			String titleCache = taxon.getTitleCache();
383
    			String[] split = titleCache.split("\\s+");
384
    			for(String s:split){
385
    				genusOrUninomial = s;
386
    				break;
387
    			}
388
    		}
389
    		//if node has children
390

    
391
    		//retrieve list from map if not create List
392
    		if(sortedGenusMap.containsKey(genusOrUninomial)){
393
    			List<TaxonNode> list = sortedGenusMap.get(genusOrUninomial);
394
    			list.add(node);
395
    			sortedGenusMap.put(genusOrUninomial, list);
396
    		}else{
397
    			//create List for genus
398
    			List<TaxonNode> list = new ArrayList<TaxonNode>();
399
    			list.add(node);
400
    			sortedGenusMap.put(genusOrUninomial, list);
401
    		}
402
    	}
403
    	return sortedGenusMap;
404
    }
405

    
406
    /**
407
     *
408
     * creates new Classification and parent TaxonNodes at genus level
409
     *
410
     *
411
     * @param map GenusMap which holds a name (Genus) and all the same Taxa as a list
412
     * @param classification you want to improve the hierarchy (will not be modified)
413
     * @param configurator to change certain settings, if null then standard settings will be taken
414
     * @return new classification with parentNodes for each entry in the map
415
     */
416
    @SuppressWarnings({ "rawtypes", "unchecked" })
417
	@Transactional(readOnly = false)
418
	@Override
419
    public UpdateResult createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator){
420
        UpdateResult result = new UpdateResult();
421
    	classification = dao.findByUuid(classification.getUuid());
422
    	Map<String, List<TaxonNode>> map = getSortedGenusList(classification.getAllNodes());
423

    
424
    	final String APPENDIX = "repaired";
425
    	String titleCache = org.apache.commons.lang.StringUtils.isBlank(classification.getTitleCache()) ? " " : classification.getTitleCache() ;
426
    	//TODO classification clone???
427
    	Classification newClassification = Classification.NewInstance(titleCache +" "+ APPENDIX);
428
    	newClassification.setReference(classification.getReference());
429

    
430
    	for(Map.Entry<String, List<TaxonNode>> entry:map.entrySet()){
431
    		String genus = entry.getKey();
432
    		List<TaxonNode> listOfTaxonNodes = entry.getValue();
433
    		TaxonNode parentNode = null;
434
    		//Search for genus in list
435
    		for(TaxonNode tNode:listOfTaxonNodes){
436
    			//take that taxonNode as parent and remove from list with all it possible children
437
    			//FIXME NPE for name
438
    			TaxonNameBase name = tNode.getTaxon().getName();
439
				NonViralName nonViralName = CdmBase.deproxy(name, NonViralName.class);
440
    			if(nonViralName.getNameCache().equalsIgnoreCase(genus)){
441
    				TaxonNode clone = (TaxonNode) tNode.clone();
442
    				if(!tNode.hasChildNodes()){
443
    					//FIXME remove classification
444
//    					parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
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
    				}else{
450
    					//get all childNodes
451
    					//save prior Hierarchy and remove them from the list
452
    					List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tNode, clone, result);
453
//    					parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference());
454
      					//FIXME remove classification
455
    					parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference());
456
    					//remove taxonNode from list because just added to classification
457
    					result.addUpdatedObject(tNode);
458
    					listOfTaxonNodes.remove(tNode);
459
    					if(copyAllChildrenToTaxonNode != null){
460
    						listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
461
    					}
462
    				}
463
    				break;
464
    			}
465
    		}
466
    		if(parentNode == null){
467
    			//if no match found in list, create parentNode
468
    			NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance();
469
    			NonViralName nonViralName = parser.parseFullName(genus);
470
    			TaxonNameBase taxonNameBase = nonViralName;
471
    			//TODO Sec via configurator
472
    			Taxon taxon = Taxon.NewInstance(taxonNameBase, null);
473
    			parentNode = newClassification.addChildTaxon(taxon, 0, null, null);
474
    			result.addUpdatedObject(parentNode);
475
    		}
476
    		//iterate over the rest of the list
477
    		for(TaxonNode tn : listOfTaxonNodes){
478
    			//if TaxonNode has a parent and this is not the classification then skip it
479
    			//and add to new classification via the parentNode as children of it
480
    			//this should assures to keep the already existing hierarchy
481
    			//FIXME: Assert is not rootnode --> entrypoint is not classification in future but rather rootNode
482

    
483
    			if(!tn.isTopmostNode()){
484
    				continue; //skip to next taxonNode
485
    			}
486

    
487
    			TaxonNode clone = (TaxonNode) tn.clone();
488
    			//FIXME: citation from node
489
    			//TODO: addchildNode without citation and references
490
//    			TaxonNode taxonNode = parentNode.addChildNode(clone, classification.getCitation(), classification.getMicroReference());
491
    			TaxonNode taxonNode = parentNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
492
    			result.addUnChangedObject(clone);
493
    			if(tn.hasChildNodes()){
494
    				//save hierarchy in new classification
495
    				List<TaxonNode> copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tn, taxonNode, result);
496
    				if(copyAllChildrenToTaxonNode != null){
497
    					listOfTaxonNodes = (List<TaxonNode>) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode);
498
    				}
499
    			}
500
    		}
501
    	}
502
    	dao.saveOrUpdate(newClassification);
503
    	result.setCdmEntity(newClassification);
504
    	return result;
505
    }
506

    
507
    /**
508
     *
509
     * recursive method to get all childnodes of taxonNode in classification.
510
     *
511
     * @param classification just for References and Citation, can be null
512
     * @param copyFromNode TaxonNode with Children
513
     * @param copyToNode TaxonNode which will receive the children
514
     * @return List of ChildNode which has been added. If node has no children returns null
515
     */
516
   private List<TaxonNode> copyAllChildrenToTaxonNode(TaxonNode copyFromNode, TaxonNode copyToNode, UpdateResult result) {
517
		List<TaxonNode> childNodes;
518
		if(!copyFromNode.hasChildNodes()){
519
			return null;
520
		}else{
521
			childNodes = copyFromNode.getChildNodes();
522
		}
523
		for(TaxonNode childNode:childNodes){
524
			TaxonNode clone = (TaxonNode) childNode.clone();
525
			result.addUnChangedObject(clone);
526
			if(childNode.hasChildNodes()){
527
				copyAllChildrenToTaxonNode(childNode, clone, result);
528
			}
529
			//FIXME: citation from node instead of classification
530
//			copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference());
531
			copyToNode.addChildNode(clone, clone.getReference(), clone.getMicroReference());
532
		}
533
		return childNodes;
534
	}
535

    
536
}
(7-7/92)