Revision 3c420001
Added by Andreas Müller over 1 year ago
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/reference/CdmLinkSource.java | ||
---|---|---|
94 | 94 |
} |
95 | 95 |
} |
96 | 96 |
|
97 |
//maybe only workaround #9801 |
|
98 |
public boolean hasNoTarget(){ |
|
99 |
return taxon == null && description == null; |
|
100 |
} |
|
101 |
|
|
97 | 102 |
public void setTarget(ICdmTarget target) { |
98 | 103 |
target = CdmBase.deproxy(target); |
99 |
if (target instanceof DescriptionBase<?>){ |
|
104 |
if (target == null){ |
|
105 |
setToNull(); //workaround? #9801 |
|
106 |
}else if (target instanceof DescriptionBase<?>){ |
|
100 | 107 |
this.description = (DescriptionBase<?>)target; |
101 | 108 |
}else if (target instanceof Taxon){ |
102 | 109 |
this.taxon = (Taxon)target; |
... | ... | |
105 | 112 |
} |
106 | 113 |
} |
107 | 114 |
|
115 |
//workaround? #9801 |
|
116 |
private void setToNull() { |
|
117 |
this.description = null; |
|
118 |
this.taxon = null; |
|
119 |
} |
|
120 |
|
|
108 | 121 |
// ********************************* CLONE **********************************/ |
109 | 122 |
|
110 | 123 |
@Override |
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/reference/OriginalSourceBase.java | ||
---|---|---|
226 | 226 |
if (cdmTarget != null){ |
227 | 227 |
this.cdmSource = CdmLinkSource.NewInstance(cdmTarget); |
228 | 228 |
}else{ |
229 |
if (cdmSource != null){ |
|
230 |
cdmSource.setTarget(null); //as long as orphan-removal does not work #9801 |
|
231 |
} |
|
229 | 232 |
this.cdmSource = null; |
230 | 233 |
} |
231 | 234 |
} |
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/DescriptionServiceImpl.java | ||
---|---|---|
684 | 684 |
} |
685 | 685 |
} |
686 | 686 |
if (deleteResult.isOk() ){ |
687 |
CdmBase.deproxy(description); |
|
687 | 688 |
if (description instanceof TaxonDescription){ |
688 |
TaxonDescription taxDescription = HibernateProxyHelper.deproxy(description, TaxonDescription.class);
|
|
689 |
TaxonDescription taxDescription = (TaxonDescription)description;
|
|
689 | 690 |
Taxon tax = taxDescription.getTaxon(); |
690 |
tax.removeDescription(taxDescription, true); |
|
691 |
deleteResult.addUpdatedObject(tax); |
|
691 |
if (tax != null){ |
|
692 |
tax.removeDescription(taxDescription, true); |
|
693 |
deleteResult.addUpdatedObject(tax); |
|
694 |
} |
|
692 | 695 |
} |
693 |
else if (HibernateProxyHelper.isInstanceOf(description, SpecimenDescription.class)){
|
|
694 |
SpecimenDescription specimenDescription = HibernateProxyHelper.deproxy(description, SpecimenDescription.class);
|
|
696 |
else if (description instanceof SpecimenDescription){
|
|
697 |
SpecimenDescription specimenDescription = (SpecimenDescription)description;
|
|
695 | 698 |
SpecimenOrObservationBase<?> specimen = specimenDescription.getDescribedSpecimenOrObservation(); |
696 |
specimen.removeDescription(specimenDescription); |
|
697 |
deleteResult.addUpdatedObject(specimen); |
|
699 |
if (specimen != null){ |
|
700 |
specimen.removeDescription(specimenDescription); |
|
701 |
deleteResult.addUpdatedObject(specimen); |
|
702 |
} |
|
698 | 703 |
} |
699 | 704 |
|
700 | 705 |
for (DescriptiveDataSet dataset : description.getDescriptiveDataSets()) { |
... | ... | |
735 | 740 |
continue; |
736 | 741 |
} else if (ref instanceof DescriptionElementBase){ |
737 | 742 |
continue; |
743 |
} else if (ref instanceof CdmLinkSource && ((CdmLinkSource)ref).hasNoTarget()) { |
|
744 |
continue; //maybe only workaround #9801 |
|
738 | 745 |
}else { |
739 | 746 |
String message = "The description can't be completely deleted because it is referenced by " + ref.getUserFriendlyTypeName() ; |
740 | 747 |
result.setAbort(); |
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/description/DescriptionAggregationBase.java | ||
---|---|---|
23 | 23 |
import org.springframework.transaction.support.DefaultTransactionDefinition; |
24 | 24 |
|
25 | 25 |
import eu.etaxonomy.cdm.api.application.ICdmRepository; |
26 |
import eu.etaxonomy.cdm.api.service.DeleteResult; |
|
26 | 27 |
import eu.etaxonomy.cdm.api.service.IClassificationService; |
27 | 28 |
import eu.etaxonomy.cdm.api.service.IDescriptionService; |
28 | 29 |
import eu.etaxonomy.cdm.api.service.IDescriptiveDataSetService; |
... | ... | |
283 | 284 |
private void deleteDescriptionsToDelete(DescriptionAggregationBase<T, CONFIG>.ResultHolder resultHolder) { |
284 | 285 |
for (DescriptionBase<?> descriptionToDelete : resultHolder.descriptionsToDelete){ |
285 | 286 |
if (descriptionToDelete.isPersited()){ |
286 |
repository.getDescriptionService().delete(descriptionToDelete); |
|
287 |
getSession().flush(); // move to service method #9801 |
|
288 |
DeleteResult result = repository.getDescriptionService().deleteDescription(descriptionToDelete); |
|
289 |
//TODO handle result somehow if not OK, but careful, descriptions may be linked >1x and therefore maybe deleted only after last link was removed |
|
287 | 290 |
} |
288 | 291 |
} |
289 | 292 |
} |
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/description/StructuredDescriptionAggregation.java | ||
---|---|---|
41 | 41 |
import eu.etaxonomy.cdm.model.description.StatisticalMeasurementValue; |
42 | 42 |
import eu.etaxonomy.cdm.model.description.TaxonDescription; |
43 | 43 |
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase; |
44 |
import eu.etaxonomy.cdm.model.reference.ICdmTarget; |
|
45 | 44 |
import eu.etaxonomy.cdm.model.reference.OriginalSourceType; |
46 | 45 |
import eu.etaxonomy.cdm.model.taxon.Taxon; |
47 | 46 |
import eu.etaxonomy.cdm.model.taxon.TaxonNode; |
... | ... | |
177 | 176 |
//remove remaining sources-to-be-removed |
178 | 177 |
for (IdentifiableSource sourceToRemove : sourcesToRemove) { |
179 | 178 |
targetDescription.removeSource(sourceToRemove); |
180 |
ICdmTarget target = sourceToRemove.getCdmSource();
|
|
179 |
ICdmBase target = CdmBase.deproxy(sourceToRemove.getCdmSource());
|
|
181 | 180 |
if (target != null){ |
182 |
if (target.isInstanceOf(DescriptionBase.class)){ |
|
181 |
sourceToRemove.setCdmSource(null); //workaround for missing orphan removal #9801 |
|
182 |
if (target instanceof DescriptionBase){ |
|
183 | 183 |
@SuppressWarnings("unchecked") |
184 | 184 |
T descriptionToDelete = (T)target; |
185 |
((IDescribable<T>)descriptionToDelete.describedEntity()).removeDescription(descriptionToDelete); |
|
186 |
structuredResultHolder.descriptionsToDelete.add(descriptionToDelete); |
|
185 |
if (descriptionToDelete.isCloneForSource()){ |
|
186 |
//TODO maybe this is not really needed as it is later done anyway with .deltedDescription |
|
187 |
//but currently this still leads to an re-saved by cascade exception |
|
188 |
((IDescribable<T>)descriptionToDelete.describedEntity()).removeDescription(descriptionToDelete); |
|
189 |
structuredResultHolder.descriptionsToDelete.add(descriptionToDelete); |
|
190 |
} |
|
187 | 191 |
}else if (target.isInstanceOf(Taxon.class)){ |
188 | 192 |
//nothing to do for now |
189 | 193 |
} else { |
... | ... | |
222 | 226 |
T existingTargetDesc = CdmBase.deproxy((T)mergeCandidate.getCdmSource()); |
223 | 227 |
mergeSourceDescription(existingTargetDesc, newTargetDesc); |
224 | 228 |
((IDescribable<T>)existingTargetDesc.describedEntity()).addDescription(existingTargetDesc); |
225 |
((IDescribable<T>)newTargetDesc.describedEntity()).removeDescription(newTargetDesc); |
|
229 |
if (!existingTargetDesc.equals(newTargetDesc)){ |
|
230 |
((IDescribable<T>)newTargetDesc.describedEntity()).removeDescription(newTargetDesc); |
|
231 |
} |
|
226 | 232 |
}else if (newTarget instanceof Taxon){ |
227 | 233 |
//nothing to do for now (we do not support reuse of sources linking to different taxa yet) |
228 | 234 |
}else{ |
... | ... | |
312 | 318 |
} |
313 | 319 |
|
314 | 320 |
private <T extends DescriptionBase<?>> T cloneNewSourceDescription(T newSourceDescription) { |
321 |
if (!getConfig().isCloneAggregatedSourceDescriptions() && newSourceDescription.isAggregatedStructuredDescription()){ |
|
322 |
return newSourceDescription; |
|
323 |
} |
|
315 | 324 |
@SuppressWarnings("unchecked") |
316 | 325 |
T clonedDescription = (T)newSourceDescription.clone(); |
326 |
// clonedDescription.removeSources(); |
|
317 | 327 |
clonedDescription.removeDescriptiveDataSet(dataSet); |
318 | 328 |
clonedDescription.getTypes().add(DescriptionType.CLONE_FOR_SOURCE); |
319 | 329 |
clonedDescription.setTitleCache("Clone: " + clonedDescription.getTitleCache(), true); |
cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/description/StructuredDescriptionAggregationConfiguration.java | ||
---|---|---|
27 | 27 |
|
28 | 28 |
boolean includeDefault = true; |
29 | 29 |
boolean includeLiterature = true; |
30 |
/** |
|
31 |
* If source mode is {@link AggregationSourceMode#DESCRIPTION} descriptions |
|
32 |
* are cloned as sources. This parameter defines if aggregated descriptions |
|
33 |
* being the sources for further aggregation should also be cloned or |
|
34 |
* can be handled as stable as usually they are not changing overtime |
|
35 |
* or stability is not a requirement. |
|
36 |
* TODO maybe we want to move it to base class |
|
37 |
* TODO maybe we need the same for non-aggregated descriptions |
|
38 |
* (generell or specific for specimen, literature and/or default descriptions). |
|
39 |
*/ |
|
40 |
boolean cloneAggregatedSourceDescriptions = false; |
|
30 | 41 |
|
31 | 42 |
private MissingMinimumMode missingMinimumMode = MissingMinimumMode.MinToZero; |
32 | 43 |
private MissingMaximumMode missingMaximumMode = MissingMaximumMode.MaxToMin; |
... | ... | |
102 | 113 |
public void setMissingMaximumMode(MissingMaximumMode missingMaximumMode) { |
103 | 114 |
this.missingMaximumMode = missingMaximumMode; |
104 | 115 |
} |
116 |
|
|
117 |
public boolean isCloneAggregatedSourceDescriptions() { |
|
118 |
// TODO Auto-generated method stub |
|
119 |
return false; |
|
120 |
} |
|
105 | 121 |
} |
cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/description/StructuredDescriptionAggregationTest.java | ||
---|---|---|
174 | 174 |
UpdateResult result = engine.invoke(config, repository); |
175 | 175 |
verifyStatusOk(result); |
176 | 176 |
commitAndStartNewTransaction(); |
177 |
verifyAggregatedDescription(new TestConfig()); |
|
177 |
verifyAggregatedDescription(new TestConfig(config));
|
|
178 | 178 |
|
179 | 179 |
addSomeDataToFirstAggregation(); |
180 | 180 |
commitAndStartNewTransaction(); |
181 |
verifyAggregatedDescription(new TestConfig().setWithAddedData()); |
|
181 |
verifyAggregatedDescription(new TestConfig(config).setWithAddedData());
|
|
182 | 182 |
|
183 | 183 |
// 2nd aggregation => should be same as originally as data was only added to aggregation to see if it is correctly deleted |
184 | 184 |
result = engine.invoke(config, repository); |
185 | 185 |
verifyStatusOk(result); |
186 | 186 |
commitAndStartNewTransaction(); |
187 |
verifyAggregatedDescription(new TestConfig()); |
|
187 |
verifyAggregatedDescription(new TestConfig(config));
|
|
188 | 188 |
|
189 | 189 |
// reaggregate with an element having a feature not yet in the existing descriptions |
190 | 190 |
addNewFeature(); |
... | ... | |
192 | 192 |
result = engine.invoke(config, repository); |
193 | 193 |
verifyStatusOk(result); |
194 | 194 |
commitAndStartNewTransaction(); |
195 |
verifyAggregatedDescription(new TestConfig().setWithFeature()); |
|
195 |
verifyAggregatedDescription(new TestConfig(config).setWithFeature());
|
|
196 | 196 |
|
197 | 197 |
} |
198 | 198 |
|
... | ... | |
233 | 233 |
UpdateResult result = engine.invoke(config, repository); |
234 | 234 |
verifyStatusOk(result); |
235 | 235 |
commitAndStartNewTransaction(); |
236 |
verifyAggregatedDescription(new TestConfig()); |
|
236 |
verifyAggregatedDescription(new TestConfig(config));
|
|
237 | 237 |
|
238 | 238 |
removeSomeDataFromFirstAggregation(); |
239 | 239 |
commitAndStartNewTransaction(); |
240 | 240 |
Assert.assertEquals("Should have 3 specimen desc, 1 literature desc, 2 individual association holder, " |
241 |
+ "4 aggregated descriptions, 4 cloned specimen descriptions (still not deleted), (3 cloned aggregated descriptions?) = 17",
|
|
242 |
17, descriptionService.count(null));
|
|
241 |
+ "4 aggregated descriptions, 4 cloned specimen descriptions (still not deleted), (0(3) cloned aggregated descriptions?) = 14",
|
|
242 |
14, descriptionService.count(null));
|
|
243 | 243 |
|
244 | 244 |
// 2nd aggregation |
245 | 245 |
result = engine.invoke(config, repository); |
246 | 246 |
verifyStatusOk(result); |
247 | 247 |
commitAndStartNewTransaction(); |
248 |
verifyAggregatedDescription(new TestConfig().setWithRemoved()); |
|
248 |
verifyAggregatedDescription(new TestConfig(config).setWithRemoved());
|
|
249 | 249 |
} |
250 | 250 |
|
251 | 251 |
private void removeSomeDataFromFirstAggregation() { |
... | ... | |
348 | 348 |
UpdateResult result = engine.invoke(config, repository); |
349 | 349 |
verifyStatusOk(result); |
350 | 350 |
commitAndStartNewTransaction(); |
351 |
verifyAggregatedDescription(new TestConfig().setAggConfig(config));
|
|
351 |
verifyAggregatedDescription(new TestConfig(config)); |
|
352 | 352 |
|
353 | 353 |
config.setWithinTaxonSourceMode(AggregationSourceMode.DESCRIPTION); |
354 | 354 |
config.setToParentSourceMode(AggregationSourceMode.NONE); |
355 | 355 |
result = engine.invoke(config, repository); |
356 | 356 |
verifyStatusOk(result); |
357 | 357 |
commitAndStartNewTransaction(); |
358 |
verifyAggregatedDescription(new TestConfig().setAggConfig(config));
|
|
358 |
verifyAggregatedDescription(new TestConfig(config)); |
|
359 | 359 |
|
360 | 360 |
config.setWithinTaxonSourceMode(AggregationSourceMode.NONE); |
361 | 361 |
config.setToParentSourceMode(AggregationSourceMode.DESCRIPTION); |
362 | 362 |
result = engine.invoke(config, repository); |
363 | 363 |
verifyStatusOk(result); |
364 | 364 |
commitAndStartNewTransaction(); |
365 |
verifyAggregatedDescription(new TestConfig().setAggConfig(config));
|
|
365 |
verifyAggregatedDescription(new TestConfig(config)); |
|
366 | 366 |
|
367 | 367 |
config.setWithinTaxonSourceMode(AggregationSourceMode.DESCRIPTION); |
368 | 368 |
config.setToParentSourceMode(AggregationSourceMode.TAXON); |
369 | 369 |
result = engine.invoke(config, repository); |
370 | 370 |
verifyStatusOk(result); |
371 | 371 |
commitAndStartNewTransaction(); |
372 |
verifyAggregatedDescription(new TestConfig().setAggConfig(config));
|
|
372 |
verifyAggregatedDescription(new TestConfig(config)); |
|
373 | 373 |
|
374 | 374 |
config.setWithinTaxonSourceMode(AggregationSourceMode.NONE); |
375 | 375 |
config.setToParentSourceMode(AggregationSourceMode.TAXON); |
376 | 376 |
result = engine.invoke(config, repository); |
377 | 377 |
verifyStatusOk(result); |
378 | 378 |
commitAndStartNewTransaction(); |
379 |
verifyAggregatedDescription(new TestConfig().setAggConfig(config));
|
|
379 |
verifyAggregatedDescription(new TestConfig(config)); |
|
380 | 380 |
|
381 | 381 |
config.setWithinTaxonSourceMode(AggregationSourceMode.ALL); |
382 | 382 |
config.setToParentSourceMode(AggregationSourceMode.DESCRIPTION); |
... | ... | |
425 | 425 |
UpdateResult result = engine.invoke(config, repository); |
426 | 426 |
commitAndStartNewTransaction(); |
427 | 427 |
verifyStatusOk(result); |
428 |
verifyAggregatedDescription(new TestConfig()); |
|
428 |
verifyAggregatedDescription(new TestConfig(config));
|
|
429 | 429 |
|
430 | 430 |
config.setIncludeLiterature(true); |
431 | 431 |
|
432 | 432 |
result = engine.invoke(config, repository); |
433 | 433 |
commitAndStartNewTransaction(); |
434 | 434 |
verifyStatusOk(result); |
435 |
verifyAggregatedDescription(new TestConfig().setWithLiterature());
|
|
435 |
verifyAggregatedDescription(new TestConfig(config));
|
|
436 | 436 |
} |
437 | 437 |
|
438 | 438 |
private void verifyStatusOk(UpdateResult result) { |
... | ... | |
472 | 472 |
|
473 | 473 |
private class TestConfig{ |
474 | 474 |
boolean withAddedData; |
475 |
boolean withLiterature; |
|
476 | 475 |
boolean withRemovedData; |
477 | 476 |
boolean withFeature; |
478 |
AggregationSourceMode withinTaxonSourceMode = AggregationSourceMode.DESCRIPTION; |
|
479 |
AggregationSourceMode toParentSourceMode = AggregationSourceMode.DESCRIPTION; |
|
477 |
final boolean withLiterature; |
|
478 |
final boolean cloneAggSourceDesc; |
|
479 |
final AggregationSourceMode withinTaxonSourceMode; |
|
480 |
final AggregationSourceMode toParentSourceMode; |
|
480 | 481 |
|
481 |
private TestConfig setWithAddedData() {withAddedData = true; return this;} |
|
482 |
private TestConfig setWithLiterature() {withLiterature = true; return this;} |
|
483 |
private TestConfig setWithRemoved() {withRemovedData = true; return this;} |
|
484 |
private TestConfig setWithFeature() {withFeature = true; return this;} |
|
485 | 482 |
|
486 |
public TestConfig setAggConfig(StructuredDescriptionAggregationConfiguration config) {
|
|
483 |
private TestConfig(StructuredDescriptionAggregationConfiguration config) {
|
|
487 | 484 |
withinTaxonSourceMode = config.getWithinTaxonSourceMode(); |
488 | 485 |
toParentSourceMode = config.getToParentSourceMode(); |
489 |
return this; |
|
486 |
cloneAggSourceDesc = config.isCloneAggregatedSourceDescriptions(); |
|
487 |
withLiterature = config.isIncludeLiterature(); |
|
490 | 488 |
} |
489 |
private TestConfig setWithAddedData() {withAddedData = true; return this;} |
|
490 |
private TestConfig setWithRemoved() {withRemovedData = true; return this;} |
|
491 |
private TestConfig setWithFeature() {withFeature = true; return this;} |
|
491 | 492 |
} |
492 | 493 |
|
493 | 494 |
private void verifyAggregatedDescription(TestConfig config) { |
... | ... | |
499 | 500 |
boolean withFeature = config.withFeature; |
500 | 501 |
boolean isToParentNone = config.toParentSourceMode.isNone(); |
501 | 502 |
boolean isToParentTaxon = config.toParentSourceMode.isTaxon(); |
503 |
boolean cloneAggSourceDesc = config.cloneAggSourceDesc; |
|
504 |
|
|
502 | 505 |
int intDel = withRemovedData? -1 : 0; |
503 | 506 |
int intLit = withLiterature? 1 : 0; |
504 | 507 |
|
... | ... | |
562 | 565 |
if (nToParentDescs > 0){ |
563 | 566 |
Assert.assertEquals(1, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_ALPINA_UUID).size()); |
564 | 567 |
Assert.assertEquals(1, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_ADENOPHORA_UUID).size()); |
565 |
Assert.assertNotEquals(aggrDescLapsanaCommunisAlpina, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_ALPINA_UUID).get(0)); |
|
568 |
if (cloneAggSourceDesc){ |
|
569 |
Assert.assertNotEquals(aggrDescLapsanaCommunisAlpina, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_ALPINA_UUID).get(0)); |
|
570 |
}else{ |
|
571 |
Assert.assertEquals(aggrDescLapsanaCommunisAlpina, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_ALPINA_UUID).get(0)); |
|
572 |
} |
|
566 | 573 |
}else if (isToParentTaxon){ |
567 | 574 |
Map<UUID, List<Taxon>> taxonToTaxonSourceMap = getSourceTaxonMap(aggrDescLapsanaCommunis); |
568 | 575 |
Assert.assertEquals(1, taxonToTaxonSourceMap.get(T_LAPSANA_COMMUNIS_ALPINA_UUID).size()); |
... | ... | |
587 | 594 |
Assert.assertEquals(nToParentDescs, taxonDescriptionMap.size()); |
588 | 595 |
if (nToParentDescs > 0){ |
589 | 596 |
Assert.assertEquals(1, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_UUID).size()); |
590 |
Assert.assertNotEquals(aggrDescLapsanaCommunis, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_UUID).get(0)); |
|
597 |
if (cloneAggSourceDesc){ |
|
598 |
Assert.assertNotEquals(aggrDescLapsanaCommunis, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_UUID).get(0)); |
|
599 |
}else{ |
|
600 |
Assert.assertEquals(aggrDescLapsanaCommunis, taxonDescriptionMap.get(T_LAPSANA_COMMUNIS_UUID).get(0)); |
|
601 |
} |
|
602 |
|
|
591 | 603 |
}else if (isToParentTaxon){ |
592 | 604 |
Map<UUID, List<Taxon>> taxonToTaxonSourceMap = getSourceTaxonMap(aggrDescLapsana); |
593 | 605 |
Assert.assertEquals(1, taxonToTaxonSourceMap.get(T_LAPSANA_COMMUNIS_UUID).size()); |
... | ... | |
595 | 607 |
} |
596 | 608 |
|
597 | 609 |
//total description count |
598 |
nCloned = (isToParentNone || isToParentTaxon ? 0 : 3) + (isWithinNone ? 0 : 4); |
|
610 |
nCloned = (isToParentNone || isToParentTaxon || !cloneAggSourceDesc ? 0 : 3) + (isWithinNone ? 0 : 4);
|
|
599 | 611 |
Assert.assertEquals("Should have 4 specimen desc, 1 literature desc, 2 individual association holder, " |
600 | 612 |
+ "4 aggregated descriptions, 4/0 cloned specimen descriptions, (3/4/0 cloned aggregated descriptions?) = 18/19", |
601 | 613 |
11+nCloned+intLit+(intDel*2), descriptionService.count(null)); |
Also available in: Unified diff
ref #9801, ref #7980, ref #8871 remove aggregated source descriptions from aggregation and fix persistence issues