From 48dde342f7c2d9e8319e984bae9610f65d4b71ee Mon Sep 17 00:00:00 2001 From: Alexander Oppermann Date: Wed, 28 Jan 2015 16:42:45 +0000 Subject: [PATCH] created new service in classificationService it is a first draft of the createHierarchyService based on taxonNames and until genus level. --- .gitattributes | 3 + .../service/ClassificationServiceImpl.java | 171 +++++++++++++++ .../api/service/IClassificationService.java | 3 + ...ierarchyForClassificationConfigurator.java | 30 +++ .../service/ClassifcationServiceDeepTest.java | 195 ++++++++++++++++++ .../service/ClassifcationServiceDeepTest.xml | 38 ++++ 6 files changed, 440 insertions(+) create mode 100644 cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/CreateHierarchyForClassificationConfigurator.java create mode 100644 cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.java create mode 100644 cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.xml diff --git a/.gitattributes b/.gitattributes index cc7728755a..df274e3312 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2259,6 +2259,7 @@ cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/UserService.java -tex cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/VersionableServiceBase.java -text cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/VocabularyServiceImpl.java -text cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/WorkingSetService.java -text +cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/CreateHierarchyForClassificationConfigurator.java -text cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/DeleteConfiguratorBase.java -text cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/FindTaxaAndNamesConfiguratorImpl.java -text cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/IFindTaxaAndNamesConfigurator.java -text @@ -2355,6 +2356,7 @@ cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/facade/DerivedUnitFacadeCache cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/facade/DerivedUnitFacadeFieldUnitCacheStrategyTest.java -text cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/facade/DerivedUnitFacadeTest.java -text cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/AbstractSecurityTestBase.java -text +cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.java -text cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassificationServiceImplTest.java -text cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/CommonServiceImplTest.java -text cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/DescriptionServiceImplTest.java -text @@ -2422,6 +2424,7 @@ cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/facade/DerivedUnitFacade cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/facade/DerivedUnitFacadeTest.testSetFieldObjectImageGallery.xml -text cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/facade/DerivedUnitFacadeTest.xml -text cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/BlankDataSet.xml -text +cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.xml -text cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassificationServiceImplTest.xml -text cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/CommonServiceImplTest.xml -text cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/FeatureNodeServiceImplTest-indexing.xml -text diff --git a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/ClassificationServiceImpl.java b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/ClassificationServiceImpl.java index 25e33289dc..99dae38552 100644 --- a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/ClassificationServiceImpl.java +++ b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/ClassificationServiceImpl.java @@ -14,16 +14,19 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.UUID; +import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import eu.etaxonomy.cdm.api.service.config.CreateHierarchyForClassificationConfigurator; import eu.etaxonomy.cdm.api.service.pager.Pager; import eu.etaxonomy.cdm.api.service.pager.PagerUtils; import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl; @@ -35,7 +38,9 @@ import eu.etaxonomy.cdm.model.description.TaxonDescription; import eu.etaxonomy.cdm.model.media.Media; import eu.etaxonomy.cdm.model.media.MediaRepresentation; import eu.etaxonomy.cdm.model.media.MediaUtils; +import eu.etaxonomy.cdm.model.name.NonViralName; import eu.etaxonomy.cdm.model.name.Rank; +import eu.etaxonomy.cdm.model.name.TaxonNameBase; import eu.etaxonomy.cdm.model.taxon.Classification; import eu.etaxonomy.cdm.model.taxon.ITaxonNodeComparator; import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode; @@ -47,6 +52,7 @@ import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao; import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonNodeDao; import eu.etaxonomy.cdm.persistence.query.OrderHint; import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy; +import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl; /** * @author n.hoffmann @@ -343,5 +349,170 @@ public class ClassificationServiceImpl extends IdentifiableServiceBase + */ + + private Map> getSortedGenusList(Collection allNodesOfClassification){ + + if(allNodesOfClassification == null || allNodesOfClassification.isEmpty()){ + return null; + } + Map> sortedGenusMap = new HashMap>(); + for(TaxonNode node:allNodesOfClassification){ + final TaxonNode tn = node; + Taxon taxon = node.getTaxon(); + NonViralName name = CdmBase.deproxy(taxon.getName(), NonViralName.class); + String genusOrUninomial = name.getGenusOrUninomial(); + //if rank unknown split string and take first word + if(genusOrUninomial == null){ + String titleCache = taxon.getTitleCache(); + String[] split = titleCache.split("\\s+"); + for(String s:split){ + genusOrUninomial = s; + break; + } + } + //if node has children + + //retrieve list from map if not create List + if(sortedGenusMap.containsKey(genusOrUninomial)){ + List list = sortedGenusMap.get(genusOrUninomial); + list.add(node); + sortedGenusMap.put(genusOrUninomial, list); + }else{ + //create List for genus + List list = new ArrayList(); + list.add(node); + sortedGenusMap.put(genusOrUninomial, list); + } + } + return sortedGenusMap; + } + /** + * + * creates new Classification and parent TaxonNodes at genus level + * + * + * @param map GenusMap which holds a name (Genus) and all the same Taxa as a list + * @param classification you want to improve the hierarchy (will not be modified) + * @param configurator to change certain settings, if null then standard settings will be taken + * @return new classification with parentNodes for each entry in the map + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Transactional(readOnly = false) + @Override + public Classification createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator){ + classification = dao.findByUuid(classification.getUuid()); + Map> map = getSortedGenusList(classification.getAllNodes()); + + final String APPENDIX = "repaired"; + String titleCache = org.apache.commons.lang.StringUtils.isBlank(classification.getTitleCache()) ? " " : classification.getTitleCache() ; + //TODO classification clone??? + Classification newClassification = Classification.NewInstance(titleCache +" "+ APPENDIX); + newClassification.setReference(classification.getReference()); + + for(Map.Entry> entry:map.entrySet()){ + String genus = entry.getKey(); + List listOfTaxonNodes = entry.getValue(); + TaxonNode parentNode = null; + //Search for genus in list + for(TaxonNode tNode:listOfTaxonNodes){ + //take that taxonNode as parent and remove from list with all it possible children + //FIXME NPE for name + TaxonNameBase name = tNode.getTaxon().getName(); + NonViralName nonViralName = CdmBase.deproxy(name, NonViralName.class); + if(nonViralName.getNameCache().equalsIgnoreCase(genus)){ + TaxonNode clone = (TaxonNode) tNode.clone(); + if(!tNode.hasChildNodes()){ + //FIXME remove classification +// parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference()); + parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference()); + //remove taxonNode from list because just added to classification + listOfTaxonNodes.remove(tNode); + }else{ + //get all childNodes + //save prior Hierarchy and remove them from the list + List copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tNode, clone); +// parentNode = newClassification.addChildNode(clone, 0, classification.getCitation(), classification.getMicroReference()); + //FIXME remove classification + parentNode = newClassification.addChildNode(clone, 0, clone.getReference(), clone.getMicroReference()); + //remove taxonNode from list because just added to classification + listOfTaxonNodes.remove(tNode); + if(copyAllChildrenToTaxonNode != null){ + listOfTaxonNodes = (List) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode); + } + } + break; + } + } + if(parentNode == null){ + //if no match found in list, create parentNode + NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance(); + NonViralName nonViralName = parser.parseFullName(genus); + TaxonNameBase taxonNameBase = (TaxonNameBase) nonViralName; + //TODO Sec via configurator + Taxon taxon = Taxon.NewInstance(taxonNameBase, null); + parentNode = newClassification.addChildTaxon(taxon, 0, null, null); + } + //iterate over the rest of the list + for(TaxonNode tn : listOfTaxonNodes){ + //if TaxonNode has a parent and this is not the classification then skip it + //and add to new classification via the parentNode as children of it + //this should assures to keep the already existing hierarchy + //FIXME: Assert is not rootnode --> entrypoint is not classification in future but rather rootNode + + if(!tn.isTopmostNode()){ + continue; //skip to next taxonNode + } + + TaxonNode clone = (TaxonNode) tn.clone(); + //FIXME: citation from node + //TODO: addchildNode without citation and references +// TaxonNode taxonNode = parentNode.addChildNode(clone, classification.getCitation(), classification.getMicroReference()); + TaxonNode taxonNode = parentNode.addChildNode(clone, clone.getReference(), clone.getMicroReference()); + if(tn.hasChildNodes()){ + //save hierarchy in new classification + List copyAllChildrenToTaxonNode = copyAllChildrenToTaxonNode(tn, taxonNode); + if(copyAllChildrenToTaxonNode != null){ + listOfTaxonNodes = (List) CollectionUtils.removeAll(listOfTaxonNodes, copyAllChildrenToTaxonNode); + } + } + } + } + dao.saveOrUpdate(newClassification); + return newClassification; + } + + /** + * + * recursive method to get all childnodes of taxonNode in classification. + * + * @param classification just for References and Citation, can be null + * @param copyFromNode TaxonNode with Children + * @param copyToNode TaxonNode which will receive the children + * @return List of ChildNode which has been added. If node has no children returns null + */ + private List copyAllChildrenToTaxonNode(TaxonNode copyFromNode, TaxonNode copyToNode) { + List childNodes; + if(!copyFromNode.hasChildNodes()){ + return null; + }else{ + childNodes = copyFromNode.getChildNodes(); + } + for(TaxonNode childNode:childNodes){ + TaxonNode clone = (TaxonNode) childNode.clone(); + if(childNode.hasChildNodes()){ + copyAllChildrenToTaxonNode(childNode, clone); + } + //FIXME: citation from node instead of classification +// copyToNode.addChildNode(clone,classification.getCitation(), classification.getMicroReference()); + copyToNode.addChildNode(clone, clone.getReference(), clone.getMicroReference()); + } + return childNodes; + } + } diff --git a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IClassificationService.java b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IClassificationService.java index 1bc64c2ce7..e310e15f73 100644 --- a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IClassificationService.java +++ b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/IClassificationService.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import eu.etaxonomy.cdm.api.service.config.CreateHierarchyForClassificationConfigurator; import eu.etaxonomy.cdm.api.service.pager.Pager; import eu.etaxonomy.cdm.model.common.UuidAndTitleCache; import eu.etaxonomy.cdm.model.media.MediaRepresentation; @@ -270,5 +271,7 @@ public interface IClassificationService extends IIdentifiableEntityService getAllNodes(); + public Classification createHierarchyInClassification(Classification classification, CreateHierarchyForClassificationConfigurator configurator); + } diff --git a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/CreateHierarchyForClassificationConfigurator.java b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/CreateHierarchyForClassificationConfigurator.java new file mode 100644 index 0000000000..2627967b8e --- /dev/null +++ b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/config/CreateHierarchyForClassificationConfigurator.java @@ -0,0 +1,30 @@ +// $Id$ +/** +* Copyright (C) 2009 EDIT +* European Distributed Institute of Taxonomy +* http://www.e-taxonomy.eu +* +* The contents of this file are subject to the Mozilla Public License Version 1.1 +* See LICENSE.TXT at the top of this package for the full license terms. +*/ +package eu.etaxonomy.cdm.api.service.config; + +import com.sun.msv.grammar.ReferenceContainer; + +/** + * This class enables to set configuration to the createHierarchy service in classification service. + * + * @author a.oppermann + * @date 26.01.2015 + * + */ +public class CreateHierarchyForClassificationConfigurator { + + public static CreateHierarchyForClassificationConfigurator NewInstance(){ + return new CreateHierarchyForClassificationConfigurator(); + } + + /** + * TODO: Implement configuration settings + */ +} diff --git a/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.java b/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.java new file mode 100644 index 0000000000..b51bb74f0b --- /dev/null +++ b/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.java @@ -0,0 +1,195 @@ +// $Id$ +/** +* Copyright (C) 2007 EDIT +* European Distributed Institute of Taxonomy +* http://www.e-taxonomy.eu +* +* The contents of this file are subject to the Mozilla Public License Version 1.1 +* See LICENSE.TXT at the top of this package for the full license terms. +*/ + +package eu.etaxonomy.cdm.api.service; + +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.unitils.dbunit.annotation.DataSet; +import org.unitils.spring.annotation.SpringBeanByType; + +import eu.etaxonomy.cdm.model.molecular.AmplificationResult; +import eu.etaxonomy.cdm.model.molecular.DnaSample; +import eu.etaxonomy.cdm.model.molecular.Sequence; +import eu.etaxonomy.cdm.model.molecular.SingleRead; +import eu.etaxonomy.cdm.model.name.BotanicalName; +import eu.etaxonomy.cdm.model.name.NonViralName; +import eu.etaxonomy.cdm.model.name.Rank; +import eu.etaxonomy.cdm.model.name.TaxonNameBase; +import eu.etaxonomy.cdm.model.occurrence.DerivationEvent; +import eu.etaxonomy.cdm.model.occurrence.DerivationEventType; +import eu.etaxonomy.cdm.model.occurrence.DerivedUnit; +import eu.etaxonomy.cdm.model.occurrence.FieldUnit; +import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationType; +import eu.etaxonomy.cdm.model.taxon.Classification; +import eu.etaxonomy.cdm.model.taxon.Taxon; +import eu.etaxonomy.cdm.model.taxon.TaxonNode; +import eu.etaxonomy.cdm.model.taxon.TaxonNodeByNameComparator; +import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl; +import eu.etaxonomy.cdm.test.integration.CdmIntegrationTest; +import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest; + +/** + * @author n.hoffmann + * @created Sep 22, 2009 + * @version 1.0 + */ +public class ClassifcationServiceDeepTest extends CdmTransactionalIntegrationTest{ + + private static final Logger logger = Logger.getLogger(ClassifcationServiceDeepTest.class); + + @SpringBeanByType + IClassificationService service; + + @SpringBeanByType + ITaxonNodeService taxonNodeService; + private static final List NODE_INIT_STRATEGY = Arrays.asList(new String[]{ + "childNodes", + "childNodes.taxon", + "childNodes.taxon.name", + "taxon.sec", + "taxon.name.*" + }); + + private Comparator taxonNodeComparator; + + + + + @Test + @DataSet + public final void testFixHierarchy(){ + Classification classification = service.find(UUID.fromString("52b41b07-5500-43ae-82e6-ea2fd328c3d5")); + + Set taxonNodes = classification.getAllNodes();//= taxonNodeService.listAllNodesForClassification(classification, 0, null); + for (TaxonNode node: taxonNodes){ + taxonNodeService.load(node.getUuid(), NODE_INIT_STRATEGY); + } + Classification classification2 = service.createHierarchyInClassification(classification, null); + + //creating the classification was succesful + Assert.assertNotNull(classification2); + for(TaxonNode node: classification2.getAllNodes()){ + node = taxonNodeService.load(node.getUuid(), NODE_INIT_STRATEGY); + //check if TaxonNode was moved and has a new parent-child-relation + if(node.getTaxon().getUuid().equals(UUID.fromString("e8bc5566-eca6-4814-b18a-814c16c66144"))){ + //parent has new Child + Assert.assertTrue(node.getCountChildren() == 1); + //child is Griftus grifatus subsp. fikus + Assert.assertTrue(node.getChildNodes().get(0).getTaxon().getUuid().equals(UUID.fromString("15d719a2-d27d-4366-92de-0898b2f3ebc8"))); + }else if(node.getTaxon().getUuid().equals(UUID.fromString("15d719a2-d27d-4366-92de-0898b2f3ebc8"))){ + //Assert that taxon has a parent and that parent is Griftus + Assert.assertTrue(node.getParent().getTaxon().getUuid().equals(UUID.fromString("e8bc5566-eca6-4814-b18a-814c16c66144"))); + } + //check if existing parent-child-relation was not touched + else if(node.getTaxon().getUuid().equals(UUID.fromString("c1cae3aa-960e-482f-b336-f3e657e96c43"))){ + //node genus genus + Assert.assertTrue(node.getCountChildren()==3); + //check if existing parent-child-relation was not touched and has a newly created parentNode + //assert this node is not anymore in the highest taxa + Assert.assertTrue(!node.isTopmostNode()); + //assert this node has a parent + Assert.assertTrue(node.getParent()!=null); + } + } + UUID uuid = classification2.getUuid(); + logger.debug("New Classification: " + uuid.toString()); + List taxonNodes2 = taxonNodeService.listAllNodesForClassification(classification2, 0, null); + Assert.assertNotEquals(taxonNodes.size(), taxonNodes2.size()); + } + + + + @Override + @Test + @Ignore + public void createTestDataSet() throws FileNotFoundException { + + String[] stringTaxonNames= new String[]{"Griftus grifatus subsp. fikus", "Griftus", "Genus genus subsp. tri", "Genus genus subsp. alt" , + "Genus genus", "Garstig alter subsp. ekel", "Garstig", "Genus genus subsp. genus"}; + + Classification classification = Classification.NewInstance("New Classification"); + classification.setUuid(UUID.fromString("52b41b07-5500-43ae-82e6-ea2fd328c3d5")); + + //create Taxa from list + Map map = new HashMap(); + for(String strName : stringTaxonNames){ + NonViralNameParserImpl parser = NonViralNameParserImpl.NewInstance(); + TaxonNameBase nameBase = parser.parseFullName(strName); + Taxon taxon = Taxon.NewInstance(nameBase, null); + map.put(strName, taxon); + } + + //create Hierarchy + Taxon tp1 = map.get("Garstig"); + Taxon tp2 = map.get("Griftus"); + Taxon tp3 = map.get("Genus genus"); + Taxon tp4 = map.get("Griftus grifatus subsp. fikus"); + + //create parents: + TaxonNode p1 = classification.addChildTaxon(tp1, null, null); + TaxonNode p2 = classification.addChildTaxon(tp2, null, null); + TaxonNode p3 = classification.addChildTaxon(tp3, null, null); + TaxonNode p4 = classification.addChildTaxon(tp4, null, null); + + + taxonNodeService.saveOrUpdate(p1); + taxonNodeService.saveOrUpdate(p2); + taxonNodeService.saveOrUpdate(p3); + taxonNodeService.saveOrUpdate(p4); + service.saveOrUpdate(classification); + + + //create children + Taxon tc1 = map.get("Garstig alter subsp. ekel"); + Taxon tc2 = map.get("Genus genus subsp. alt"); + Taxon tc3 = map.get("Genus genus subsp. tri"); + Taxon tc4 = map.get("Genus genus subsp. genus"); + + //add to parent node + taxonNodeService.saveOrUpdate(p1.addChildTaxon(tc1, null, null)); + taxonNodeService.saveOrUpdate(p3.addChildTaxon(tc2, null, null)); + taxonNodeService.saveOrUpdate(p3.addChildTaxon(tc3, null, null)); + taxonNodeService.saveOrUpdate(p3.addChildTaxon(tc4, null, null)); + + //save classification + service.saveOrUpdate(classification); + commitAndStartNewTransaction(null); + + setComplete(); + endTransaction(); + + try { + writeDbUnitDataSetFile(new String[] { + "Classification", + "LanguageString", + "TaxonNode", + "TaxonBase", + "TaxonNameBase", + "HomotypicalGroup" + }); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } +} diff --git a/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.xml b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.xml new file mode 100644 index 0000000000..5e80b4941d --- /dev/null +++ b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/ClassifcationServiceDeepTest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.34.1