X-Git-Url: https://dev.e-taxonomy.eu/gitweb/cdmlib.git/blobdiff_plain/e321340c3ecde6dec633469e6eef75ff4740a9b5..fdf9eec2eb7b4806e1a1cdc4020db83ce65345bc:/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/TaxonServiceImpl.java diff --git a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/TaxonServiceImpl.java b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/TaxonServiceImpl.java index 086c59ccbf..d4c1b5942c 100644 --- a/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/TaxonServiceImpl.java +++ b/cdmlib-services/src/main/java/eu/etaxonomy/cdm/api/service/TaxonServiceImpl.java @@ -12,6 +12,7 @@ package eu.etaxonomy.cdm.api.service; import java.io.IOException; import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -20,22 +21,26 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import javax.persistence.EntityNotFoundException; + import org.apache.log4j.Logger; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanFilter; import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryWrapperFilter; import org.apache.lucene.search.SortField; 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.DeleteConfiguratorBase; import eu.etaxonomy.cdm.api.service.config.IFindTaxaAndNamesConfigurator; import eu.etaxonomy.cdm.api.service.config.MatchingTaxonConfigurator; -import eu.etaxonomy.cdm.api.service.config.NameDeletionConfigurator; import eu.etaxonomy.cdm.api.service.config.SynonymDeletionConfigurator; -import eu.etaxonomy.cdm.api.service.config.TaxonBaseDeletionConfigurator; import eu.etaxonomy.cdm.api.service.config.TaxonDeletionConfigurator; import eu.etaxonomy.cdm.api.service.config.TaxonNodeDeletionConfigurator.ChildHandling; import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException; @@ -43,8 +48,10 @@ import eu.etaxonomy.cdm.api.service.exception.HomotypicalGroupChangeException; import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException; import eu.etaxonomy.cdm.api.service.pager.Pager; import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl; +import eu.etaxonomy.cdm.api.service.search.ILuceneIndexToolProvider; import eu.etaxonomy.cdm.api.service.search.ISearchResultBuilder; import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearch; +import eu.etaxonomy.cdm.api.service.search.LuceneMultiSearchException; import eu.etaxonomy.cdm.api.service.search.LuceneSearch; import eu.etaxonomy.cdm.api.service.search.LuceneSearch.TopGroupsWithMaxScore; import eu.etaxonomy.cdm.api.service.search.QueryFactory; @@ -62,31 +69,41 @@ import eu.etaxonomy.cdm.model.common.IdentifiableEntity; import eu.etaxonomy.cdm.model.common.IdentifiableSource; import eu.etaxonomy.cdm.model.common.Language; import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary; +import eu.etaxonomy.cdm.model.common.OriginalSourceType; import eu.etaxonomy.cdm.model.common.RelationshipBase; import eu.etaxonomy.cdm.model.common.RelationshipBase.Direction; import eu.etaxonomy.cdm.model.common.UuidAndTitleCache; +import eu.etaxonomy.cdm.model.description.CommonTaxonName; import eu.etaxonomy.cdm.model.description.DescriptionBase; import eu.etaxonomy.cdm.model.description.DescriptionElementBase; +import eu.etaxonomy.cdm.model.description.Distribution; import eu.etaxonomy.cdm.model.description.Feature; import eu.etaxonomy.cdm.model.description.IIdentificationKey; import eu.etaxonomy.cdm.model.description.PolytomousKeyNode; +import eu.etaxonomy.cdm.model.description.PresenceAbsenceTermBase; import eu.etaxonomy.cdm.model.description.SpecimenDescription; import eu.etaxonomy.cdm.model.description.TaxonDescription; import eu.etaxonomy.cdm.model.description.TaxonInteraction; import eu.etaxonomy.cdm.model.description.TaxonNameDescription; +import eu.etaxonomy.cdm.model.location.NamedArea; 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.molecular.Amplification; 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.HomotypicalGroup; +import eu.etaxonomy.cdm.model.name.NameRelationship; import eu.etaxonomy.cdm.model.name.Rank; import eu.etaxonomy.cdm.model.name.TaxonNameBase; import eu.etaxonomy.cdm.model.name.ZoologicalName; -import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase; +import eu.etaxonomy.cdm.model.occurrence.DerivedUnit; +import eu.etaxonomy.cdm.model.occurrence.DeterminationEvent; import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase; import eu.etaxonomy.cdm.model.reference.Reference; import eu.etaxonomy.cdm.model.taxon.Classification; +import eu.etaxonomy.cdm.model.taxon.ITaxonTreeNode; import eu.etaxonomy.cdm.model.taxon.Synonym; import eu.etaxonomy.cdm.model.taxon.SynonymRelationship; import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType; @@ -95,11 +112,12 @@ import eu.etaxonomy.cdm.model.taxon.TaxonBase; import eu.etaxonomy.cdm.model.taxon.TaxonNode; import eu.etaxonomy.cdm.model.taxon.TaxonRelationship; import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType; -import eu.etaxonomy.cdm.persistence.dao.AbstractBeanInitializer; import eu.etaxonomy.cdm.persistence.dao.common.ICdmGenericDao; import eu.etaxonomy.cdm.persistence.dao.common.IOrderedTermVocabularyDao; +import eu.etaxonomy.cdm.persistence.dao.initializer.AbstractBeanInitializer; import eu.etaxonomy.cdm.persistence.dao.name.ITaxonNameDao; import eu.etaxonomy.cdm.persistence.dao.occurrence.IOccurrenceDao; +import eu.etaxonomy.cdm.persistence.dao.taxon.IClassificationDao; import eu.etaxonomy.cdm.persistence.dao.taxon.ITaxonDao; import eu.etaxonomy.cdm.persistence.fetch.CdmFetch; import eu.etaxonomy.cdm.persistence.query.MatchMode; @@ -131,6 +149,9 @@ public class TaxonServiceImpl extends IdentifiableServiceBase getAllSynonyms(int limit, int start) { - return dao.getAllSynonyms(limit, start); - } - - /** - * FIXME Candidate for harmonization - * list(Taxon.class, ...) - * (non-Javadoc) - * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllTaxa(int, int) - */ - @Override - public List getAllTaxa(int limit, int start) { - return dao.getAllTaxa(limit, start); - } - /** * FIXME Candidate for harmonization * merge with getRootTaxa(Reference sec, ..., ...) @@ -198,18 +203,11 @@ public class TaxonServiceImpl extends IdentifiableServiceBase getRootTaxa(Rank rank, Reference sec, boolean onlyWithChildren,boolean withMisapplications, List propertyPaths) { return dao.getRootTaxa(rank, sec, null, onlyWithChildren, withMisapplications, propertyPaths); } - /* (non-Javadoc) - * @see eu.etaxonomy.cdm.api.service.ITaxonService#getAllRelationships(int, int) - */ @Override public List getAllRelationships(int limit, int start){ return dao.getAllRelationships(limit, start); @@ -257,12 +255,12 @@ public class TaxonServiceImpl extends IdentifiableServiceBase acceptedName = acceptedTaxon.getName(); + + TaxonNameBase acceptedName = acceptedTaxon.getName(); TaxonNameBase synonymName = synonym.getName(); HomotypicalGroup synonymHomotypicGroup = synonymName.getHomotypicalGroup(); @@ -276,10 +274,12 @@ public class TaxonServiceImpl extends IdentifiableServiceBase heteroSynonyms = acceptedTaxon.getSynonymsInGroup(synonymHomotypicGroup); + Set basionymsAndReplacedSynonyms = synonymHomotypicGroup.getBasionymAndReplacedSynonymRelations(); for (Synonym heteroSynonym : heteroSynonyms){ if (synonym.equals(heteroSynonym)){ acceptedTaxon.removeSynonym(heteroSynonym, false); + }else{ //move synonyms in same homotypic group to new accepted taxon heteroSynonym.replaceAcceptedTaxon(newAcceptedTaxon, relTypeForGroup, copyCitationInfo, citation, microCitation); @@ -287,12 +287,14 @@ public class TaxonServiceImpl extends IdentifiableServiceBase synonymName = synonym.getName(); - // remove synonym from taxon + /* // remove synonym from taxon toTaxon.removeSynonym(synonym); - +*/ // Create a taxon with synonym name Taxon fromTaxon = Taxon.NewInstance(synonymName, null); @@ -320,7 +322,8 @@ public class TaxonServiceImpl extends IdentifiableServiceBase(pageNumber, numberOfResults, pageSize, results); } + @Override + public List listAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber, + List orderHints, List propertyPaths){ + return pageAcceptedTaxaFor(synonymUuid, classificationUuid, pageSize, pageNumber, orderHints, propertyPaths).getRecords(); + } + + @Override + public Pager pageAcceptedTaxaFor(UUID synonymUuid, UUID classificationUuid, Integer pageSize, Integer pageNumber, + List orderHints, List propertyPaths){ + + List list = new ArrayList(); + Long count = 0l; + + Synonym synonym = null; + + try { + synonym = (Synonym) dao.load(synonymUuid); + } catch (ClassCastException e){ + throw new EntityNotFoundException("The TaxonBase entity referenced by " + synonymUuid + " is not a Synonmy"); + } catch (NullPointerException e){ + throw new EntityNotFoundException("No TaxonBase entity found for " + synonymUuid); + } + + Classification classificationFilter = null; + if(classificationUuid != null){ + try { + classificationFilter = classificationDao.load(classificationUuid); + } catch (NullPointerException e){ + throw new EntityNotFoundException("No Classification entity found for " + classificationUuid); + } + if(classificationFilter == null){ + + } + } + + count = dao.countAcceptedTaxaFor(synonym, classificationFilter) ; + if(count > (pageSize * pageNumber)){ + list = dao.listAcceptedTaxaFor(synonym, classificationFilter, pageSize, pageNumber, orderHints, propertyPaths); + } + + return new DefaultPagerImpl(pageNumber, count.intValue(), pageSize, list); + } + + /** * @param taxon * @param includeRelationships @@ -530,6 +577,10 @@ public class TaxonServiceImpl extends IdentifiableServiceBase(pageNumber, numberOfResults, pageSize, results); } + /* (non-Javadoc) + * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List) + */ + @Override + public List> getSynonymsByHomotypicGroup(Taxon taxon, List propertyPaths){ + List> result = new ArrayList>(); + Taxon t = (Taxon)dao.load(taxon.getUuid(), propertyPaths); + + //homotypic + result.add(t.getHomotypicSynonymsByHomotypicGroup()); + + //heterotypic + List homotypicalGroups = t.getHeterotypicSynonymyGroups(); + for(HomotypicalGroup homotypicalGroup : homotypicalGroups){ + result.add(t.getSynonymsInGroup(homotypicalGroup)); + } + + return result; + + } + /* (non-Javadoc) * @see eu.etaxonomy.cdm.api.service.ITaxonService#getHomotypicSynonymsByHomotypicGroup(eu.etaxonomy.cdm.model.taxon.Taxon, java.util.List) */ @@ -777,6 +849,8 @@ public class TaxonServiceImpl extends IdentifiableServiceBase propertyPath) { + logger.trace("listMedia() - START"); + Set taxa = new HashSet(); List taxonMedia = new ArrayList(); @@ -785,13 +859,15 @@ public class TaxonServiceImpl extends IdentifiableServiceBase taxonDescriptions = new ArrayList(); // --- TaxonDescriptions for (Taxon t : taxa) { @@ -808,7 +884,9 @@ public class TaxonServiceImpl extends IdentifiableServiceBase specimensOrObservations = new HashSet(); // --- Specimens for (Taxon t : taxa) { @@ -816,7 +894,8 @@ public class TaxonServiceImpl extends IdentifiableServiceBase specimenDescriptions = occurrence.getSpecimenDescriptions(); @@ -832,17 +911,28 @@ public class TaxonServiceImpl extends IdentifiableServiceBase sequences = ((DnaSample) occurrence).getSequences(); + // pherograms & gelPhotos + if (occurrence.isInstanceOf(DnaSample.class)) { + DnaSample dnaSample = CdmBase.deproxy(occurrence, DnaSample.class); + Set sequences = dnaSample.getSequences(); + //we do show only those gelPhotos which lead to a consensus sequence for (Sequence sequence : sequences) { - taxonMedia.addAll(sequence.getChromatograms()); + Set dnaRelatedMedia = new HashSet(); + for (SingleRead singleRead : sequence.getSingleReads()){ + Amplification amplification = singleRead.getAmplification(); + dnaRelatedMedia.add(amplification.getGelPhoto()); + dnaRelatedMedia.add(singleRead.getPherogram()); + dnaRelatedMedia.remove(null); + } + taxonMedia.addAll(dnaRelatedMedia); } } @@ -850,6 +940,7 @@ public class TaxonServiceImpl extends IdentifiableServiceBase nameDescriptions = new HashSet(); for (Taxon t : taxa) { @@ -867,7 +958,12 @@ public class TaxonServiceImpl extends IdentifiableServiceBase 0){ - String message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator."; - throw new ReferencedObjectUndeletableException(message); - } - }else{ - Set nodes = taxon.getTaxonNodes(); - Iterator iterator = nodes.iterator(); - TaxonNode node = null; - boolean deleteChildren; - if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){ - deleteChildren = true; - }else { - deleteChildren = false; - } - if (!config.isDeleteInAllClassifications() && !(classification == null)){ - while (iterator.hasNext()){ - node = iterator.next(); - if (node.getClassification().equals(classification)){ - break; - } - node = null; - } - if (node != null){ - taxon.removeTaxonNode(node, deleteChildren); - } - } else if (config.isDeleteInAllClassifications()){ - taxon.removeTaxonNodes(deleteChildren); - } - - } - - - // SynonymRelationShip + + List referencedObjects = isDeletable(taxon, config); + + if (referencedObjects.isEmpty()){ + // --- DeleteSynonymRelations if (config.isDeleteSynonymRelations()){ boolean removeSynonymNameFromHomotypicalGroup = false; - for (SynonymRelationship synRel : taxon.getSynonymRelations()){ + // use tmp Set to avoid concurrent modification + Set synRelsToDelete = new HashSet(); + synRelsToDelete.addAll(taxon.getSynonymRelations()); + for (SynonymRelationship synRel : synRelsToDelete){ Synonym synonym = synRel.getSynonym(); + // taxon.removeSynonymRelation will set the accepted taxon and the synonym to NULL + // this will cause hibernate to delete the relationship since + // the SynonymRelationship field on both is annotated with removeOrphan + // so no further explicit deleting of the relationship should be done here taxon.removeSynonymRelation(synRel, removeSynonymNameFromHomotypicalGroup); + + // --- DeleteSynonymsIfPossible if (config.isDeleteSynonymsIfPossible()){ //TODO which value boolean newHomotypicGroupIfNeeded = true; SynonymDeletionConfigurator synConfig = new SynonymDeletionConfigurator(); - deleteSynonym(synonym, taxon, synConfig); - }else{ - deleteSynonymRelationships(synonym, taxon); } + // relationship will be deleted by hibernate automatically, + // see comment above and http://dev.e-taxonomy.eu/trac/ticket/3797 + // else{ + // deleteSynonymRelationships(synonym, taxon); + // } } } - // TaxonRelationship + // --- DeleteTaxonRelationships if (! config.isDeleteTaxonRelationships()){ if (taxon.getTaxonRelations().size() > 0){ - String message = "Taxon can't be deleted as it is related to another taxon. Remove taxon from all relations to other taxa prior to deletion."; - throw new ReferencedObjectUndeletableException(message); + String message = "Taxon can't be deleted as it is related to another taxon. " + + "Remove taxon from all relations to other taxa prior to deletion."; + // throw new ReferencedObjectUndeletableException(message); + } + } else{ + for (TaxonRelationship taxRel: taxon.getTaxonRelations()){ + if (config.isDeleteMisappliedNamesAndInvalidDesignations()){ + if (taxRel.getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR()) || taxRel.getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR())){ + if (taxon.equals(taxRel.getToTaxon())){ + this.deleteTaxon(taxRel.getFromTaxon(), config, classification); + } + } + } + taxon.removeTaxonRelation(taxRel); + /*if (taxFrom.equals(taxon)){ + try{ + this.deleteTaxon(taxTo, taxConf, classification); + } catch(DataChangeNoRollbackException e){ + logger.debug("A related taxon will not be deleted." + e.getMessage()); + } + } else { + try{ + this.deleteTaxon(taxFrom, taxConf, classification); + } catch(DataChangeNoRollbackException e){ + logger.debug("A related taxon will not be deleted." + e.getMessage()); + } + + }*/ + } + } + + // TaxonDescription + if (config.isDeleteDescriptions()){ + Set descriptions = taxon.getDescriptions(); + List removeDescriptions = new ArrayList(); + for (TaxonDescription desc: descriptions){ + //TODO use description delete configurator ? + //FIXME check if description is ALWAYS deletable + if (desc.getDescribedSpecimenOrObservation() != null){ + String message = "Taxon can't be deleted as it is used in a TaxonDescription" + + " which also describes specimens or abservations"; + //throw new ReferencedObjectUndeletableException(message); + } + removeDescriptions.add(desc); + descriptionService.delete(desc); + + } + for (TaxonDescription desc: removeDescriptions){ + taxon.removeDescription(desc); } } - - + /* //check references with only reverse mapping + String message = checkForReferences(taxon); + if (message != null){ + //throw new ReferencedObjectUndeletableException(message.toString()); + }*/ + if (! config.isDeleteTaxonNodes() || (!config.isDeleteInAllClassifications() && classification == null )){ + //if (taxon.getTaxonNodes().size() > 0){ + // message = "Taxon can't be deleted as it is used in a classification node. Remove taxon from all classifications prior to deletion or define a classification where it should be deleted or adapt the taxon deletion configurator."; + // throw new ReferencedObjectUndeletableException(message); + //} + }else{ + if (taxon.getTaxonNodes().size() != 0){ + Set nodes = taxon.getTaxonNodes(); + Iterator iterator = nodes.iterator(); + TaxonNode node = null; + boolean deleteChildren; + if (config.getTaxonNodeConfig().getChildHandling().equals(ChildHandling.DELETE)){ + deleteChildren = true; + }else { + deleteChildren = false; + } + boolean success = true; + if (!config.isDeleteInAllClassifications() && !(classification == null)){ + while (iterator.hasNext()){ + node = iterator.next(); + if (node.getClassification().equals(classification)){ + break; + } + node = null; + } + if (node != null){ + success =taxon.removeTaxonNode(node, deleteChildren); + nodeService.delete(node); + } else { + // message = "Taxon is not used in defined classification"; + // throw new DataChangeNoRollbackException(message); + } + } else if (config.isDeleteInAllClassifications()){ + Set nodesList = new HashSet(); + nodesList.addAll(taxon.getTaxonNodes()); + + for (ITaxonTreeNode treeNode: nodesList){ + TaxonNode taxonNode = (TaxonNode) treeNode; + if(!deleteChildren){ + /* Object[] childNodes = taxonNode.getChildNodes().toArray(); + //nodesList.addAll(taxonNode.getChildNodes()); + for (Object childNode: childNodes){ + TaxonNode childNodeCast = (TaxonNode) childNode; + deleteTaxon(childNodeCast.getTaxon(), config, classification); - //check references with only reverse mapping - Set referencingObjects = genericDao.getReferencingObjects(taxon); - for (CdmBase referencingObject : referencingObjects){ - //IIdentificationKeys (Media, Polytomous, MultiAccess) - if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){ - String message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name"; - message = String.format(message, CdmBase.deproxy(referencingObject, DerivedUnitBase.class).getTitleCache()); - throw new ReferencedObjectUndeletableException(message); - } + } + /*for (TaxonNode childNode: taxonNode.getChildNodes()){ + deleteTaxon(childNode.getTaxon(), config, classification); - //PolytomousKeyNode - if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){ - String message = "Taxon can't be deleted as it is used in polytomous key node"; - throw new ReferencedObjectUndeletableException(message); - } + } + // taxon.removeTaxonNode(taxonNode); + //nodeService.delete(taxonNode); + } else{ + */ + Object[] childNodes = taxonNode.getChildNodes().toArray(); + for (Object childNode: childNodes){ + TaxonNode childNodeCast = (TaxonNode) childNode; + taxonNode.getParent().addChildNode(childNodeCast, childNodeCast.getReference(), childNodeCast.getMicroReference()); + } - //TaxonInteraction - if (referencingObject.isInstanceOf(TaxonInteraction.class)){ - String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2"; - throw new ReferencedObjectUndeletableException(message); + //taxon.removeTaxonNode(taxonNode); + } + } + config.getTaxonNodeConfig().setDeleteTaxon(false); + nodeService.deleteTaxonNodes(nodesList, config); + } + if (!success){ + // message = "The taxon node could not be deleted."; + //throw new DataChangeNoRollbackException(message); + } } } + //PolytomousKey TODO + + boolean usedInPolytomousKey = checkForPolytomousKeys(taxon); //TaxonNameBase if (config.isDeleteNameIfPossible()){ - try { - TaxonNameBase name = nameService.find(taxon.getName().getUuid()); - name.removeTaxonBase(taxon); - nameService.save(name); - - nameService.delete(taxon.getName(), config.getNameDeletionConfig()); - } catch (ReferencedObjectUndeletableException e) { - //do nothing - if (logger.isDebugEnabled()){logger.debug("Name could not be deleted");} - - } + + + //TaxonNameBase name = nameService.find(taxon.getName().getUuid()); + TaxonNameBase name = (TaxonNameBase)HibernateProxyHelper.deproxy(taxon.getName()); + //check whether taxon will be deleted or not + if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) && name != null ){ + taxon = (Taxon) HibernateProxyHelper.deproxy(taxon); + name.removeTaxonBase(taxon); + nameService.save(name); + String uuidString = nameService.delete(name, config.getNameDeletionConfig()); + logger.debug(uuidString); + } + } - + // TaxonDescription - Set descriptions = taxon.getDescriptions(); + /* Set descriptions = taxon.getDescriptions(); for (TaxonDescription desc: descriptions){ if (config.isDeleteDescriptions()){ //TODO use description delete configurator ? //FIXME check if description is ALWAYS deletable - taxon.removeDescription(desc); + taxon.removeDescription(desc); descriptionService.delete(desc); }else{ if (desc.getDescribedSpecimenOrObservations().size()>0){ String message = "Taxon can't be deleted as it is used in a TaxonDescription" + - " which also describes specimens or abservations"; + " which also describes specimens or observations"; throw new ReferencedObjectUndeletableException(message); - } + } } - } - if (taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0){ - dao.delete(taxon); - } else{ - String message = "Taxon can't be deleted as it is used in a Taxonnode"; - throw new ReferencedObjectUndeletableException(message); + }*/ + + if ((taxon.getTaxonNodes() == null || taxon.getTaxonNodes().size()== 0) ){ + UUID uuid = dao.delete(taxon); + return uuid.toString(); + } else { + return "The Taxon can't be deleted."; } - + }else { + return referencedObjects.toString(); + } + } - /* (non-Javadoc) + private String checkForReferences(Taxon taxon){ + Set referencingObjects = genericDao.getReferencingObjects(taxon); + for (CdmBase referencingObject : referencingObjects){ + //IIdentificationKeys (Media, Polytomous, MultiAccess) + if (HibernateProxyHelper.isInstanceOf(referencingObject, IIdentificationKey.class)){ + String message = "Taxon" + taxon.getTitleCache() + "can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name"; + + return message; + } + + + /* //PolytomousKeyNode + if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){ + String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node"; + return message; + }*/ + + //TaxonInteraction + if (referencingObject.isInstanceOf(TaxonInteraction.class)){ + String message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2"; + return message; + } + + //TaxonInteraction + if (referencingObject.isInstanceOf(DeterminationEvent.class)){ + String message = "Taxon can't be deleted as it is used in a determination event"; + return message; + } + + } + + referencingObjects = null; + return null; + } + + private boolean checkForPolytomousKeys(Taxon taxon){ + boolean result = false; + List list = genericDao.getCdmBasesByFieldAndClass(PolytomousKeyNode.class, "taxon", taxon); + if (!list.isEmpty()) { + result = true; + } + return result; + } + + @Transactional(readOnly = false) + public UUID delete(Synonym syn){ + UUID result = syn.getUuid(); + this.deleteSynonym(syn, null); + return result; + } + + /* (non-Javadoc) * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean) */ @Transactional(readOnly = false) @Override - public void deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) { - deleteSynonym(synonym, null, config); - - } - + public String deleteSynonym(Synonym synonym, SynonymDeletionConfigurator config) { + return deleteSynonym(synonym, null, config); + + } - /* (non-Javadoc) + + /* (non-Javadoc) * @see eu.etaxonomy.cdm.api.service.ITaxonService#deleteSynonym(eu.etaxonomy.cdm.model.taxon.Synonym, eu.etaxonomy.cdm.model.taxon.Taxon, boolean, boolean) */ @Transactional(readOnly = false) @Override - public void deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) { + public String deleteSynonym(Synonym synonym, Taxon taxon, SynonymDeletionConfigurator config) { if (synonym == null){ - return; + return null; } - synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class); - - //remove synonymRelationship - Set taxonSet = new HashSet(); - if (taxon != null){ - taxonSet.add(taxon); - }else{ - taxonSet.addAll(synonym.getAcceptedTaxa()); + + if (config == null){ + config = new SynonymDeletionConfigurator(); } - for (Taxon relatedTaxon : taxonSet){ -// dao.deleteSynonymRelationships(synonym, relatedTaxon); - relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded()); + List messages = isDeletable(synonym, config); + if (messages.isEmpty()){ + synonym = CdmBase.deproxy(dao.merge(synonym), Synonym.class); + + //remove synonymRelationship + Set taxonSet = new HashSet(); + if (taxon != null){ + taxonSet.add(taxon); + }else{ + taxonSet.addAll(synonym.getAcceptedTaxa()); + } + for (Taxon relatedTaxon : taxonSet){ + // dao.deleteSynonymRelationships(synonym, relatedTaxon); + relatedTaxon.removeSynonym(synonym, config.isNewHomotypicGroupIfNeeded()); + } + this.saveOrUpdate(synonym); + + //TODO remove name from homotypical group? + + //remove synonym (if necessary) + + UUID uuid = null; + if (synonym.getSynonymRelations().isEmpty()){ + TaxonNameBase name = synonym.getName(); + synonym.setName(null); + uuid = dao.delete(synonym); + + //remove name if possible (and required) + if (name != null && config.isDeleteNameIfPossible()){ + + nameService.delete(name, config.getNameDeletionConfig()); + + } + + }else { + return null; + } + return uuid.toString(); + }else{ + return messages.toString(); } - this.saveOrUpdate(synonym); - - //TODO remove name from homotypical group? - - //remove synonym (if necessary) - - if (synonym.getSynonymRelations().isEmpty()){ - TaxonNameBase name = synonym.getName(); - synonym.setName(null); - dao.delete(synonym); - - //remove name if possible (and required) - if (name != null && config.isDeleteNameIfPossible()){ - try{ - nameService.delete(name, config.getNameDeletionConfig()); - }catch (DataChangeNoRollbackException ex){ - if (logger.isDebugEnabled()) { - logger.debug("Name wasn't deleted as it is referenced"); - } - } - } - } + } @@ -1248,6 +1494,7 @@ public class TaxonServiceImpl extends IdentifiableServiceBase orderHints, List propertyPaths) throws CorruptIndexException, IOException, ParseException { - LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments); + LuceneSearch luceneSearch = prepareFindByFullTextSearch(clazz, queryString, classification, languages, highlightFragments, null); + + // --- execute search + TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber); + + Map idFieldMap = new HashMap(); + idFieldMap.put(CdmBaseType.TAXON, "id"); + + // --- initialize taxa, thighlight matches .... + ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(luceneSearch, luceneSearch.getQuery()); + List> searchResults = searchResultBuilder.createResultSet( + topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths); + + int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0; + return new DefaultPagerImpl>(pageNumber, totalHits, pageSize, searchResults); + } + + @Override + public Pager> findByDistribution(List areaFilter, List> statusFilter, + Classification classification, + Integer pageSize, Integer pageNumber, + List orderHints, List propertyPaths) throws IOException, ParseException { + + LuceneSearch luceneSearch = prepareByDistributionSearch(areaFilter, statusFilter, classification); // --- execute search TopGroupsWithMaxScore topDocsResultSet = luceneSearch.executeSearch(pageSize, pageNumber); @@ -1414,7 +1684,7 @@ public class TaxonServiceImpl extends IdentifiableServiceBase> searchResults = searchResultBuilder.createResultSet( topDocsResultSet, luceneSearch.getHighlightFields(), dao, idFieldMap, propertyPaths); - int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupedHitCount : 0; + int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0; return new DefaultPagerImpl>(pageNumber, totalHits, pageSize, searchResults); } @@ -1424,40 +1694,467 @@ public class TaxonServiceImpl extends IdentifiableServiceBase clazz, String queryString, Classification classification, List languages, - boolean highlightFragments) { + boolean highlightFragments, SortField[] sortFields) { BooleanQuery finalQuery = new BooleanQuery(); BooleanQuery textQuery = new BooleanQuery(); - LuceneSearch luceneSearch = new LuceneSearch(getSession(), TaxonBase.class); - QueryFactory queryFactory = new QueryFactory(luceneSearch); + LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class); + QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class); - SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)}; + if(sortFields == null){ + sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)}; + } luceneSearch.setSortFields(sortFields); // ---- search criteria - luceneSearch.setClazz(clazz); + luceneSearch.setCdmTypRestriction(clazz); - textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD); - textQuery.add(queryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD); + textQuery.add(taxonBaseQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD); + textQuery.add(taxonBaseQueryFactory.newDefinedTermQuery("name.rank", queryString, languages), Occur.SHOULD); finalQuery.add(textQuery, Occur.MUST); if(classification != null){ - finalQuery.add(queryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST); + finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST); } luceneSearch.setQuery(finalQuery); if(highlightFragments){ - luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray()); + luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray()); + } + return luceneSearch; + } + + /** + * Uses org.apache.lucene.search.join.JoinUtil for query time joining, alternatively + * the BlockJoinQuery could be used. The latter might be more memory save but has the + * drawback of requiring to do the join an indexing time. + * see http://dev.e-taxonomy.eu/trac/wiki/LuceneNotes#JoinsinLucene for more information on this. + * + * Joins TaxonRelationShip with Taxon depending on the direction of the given edge: + *
    + *
  • direct, everted: {@link Direction.relatedTo}: TaxonRelationShip.relatedTo.id --> Taxon.id
  • + *
  • inverse: {@link Direction.relatedFrom}: TaxonRelationShip.relatedFrom.id --> Taxon.id
  • + *
      + * @param queryString + * @param classification + * @param languages + * @param highlightFragments + * @param sortFields TODO + * + * @return + * @throws IOException + */ + protected LuceneSearch prepareFindByTaxonRelationFullTextSearch(TaxonRelationshipEdge edge, String queryString, Classification classification, List languages, + boolean highlightFragments, SortField[] sortFields) throws IOException { + + String fromField; + String queryTermField; + String toField = "id"; // TaxonBase.uuid + + if(edge.isBidirectional()){ + throw new RuntimeException("Bidirectional joining not supported!"); + } + if(edge.isEvers()){ + fromField = "relatedFrom.id"; + queryTermField = "relatedFrom.titleCache"; + } else if(edge.isInvers()) { + fromField = "relatedTo.id"; + queryTermField = "relatedTo.titleCache"; + } else { + throw new RuntimeException("Invalid direction: " + edge.getDirections()); + } + + BooleanQuery finalQuery = new BooleanQuery(); + + LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, TaxonBase.class); + QueryFactory taxonBaseQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(TaxonBase.class); + + BooleanQuery joinFromQuery = new BooleanQuery(); + joinFromQuery.add(taxonBaseQueryFactory.newTermQuery(queryTermField, queryString), Occur.MUST); + joinFromQuery.add(taxonBaseQueryFactory.newEntityIdQuery("type.id", edge.getTaxonRelationshipType()), Occur.MUST); + Query joinQuery = taxonBaseQueryFactory.newJoinQuery(fromField, toField, joinFromQuery, TaxonRelationship.class); + + if(sortFields == null){ + sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)}; + } + luceneSearch.setSortFields(sortFields); + + finalQuery.add(joinQuery, Occur.MUST); + + if(classification != null){ + finalQuery.add(taxonBaseQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST); + } + luceneSearch.setQuery(finalQuery); + + if(highlightFragments){ + luceneSearch.setHighlightFields(taxonBaseQueryFactory.getTextFieldNamesAsArray()); + } + return luceneSearch; + } + + + + + /* (non-Javadoc) + * @see eu.etaxonomy.cdm.api.service.ITaxonService#findTaxaAndNamesByFullText(java.util.EnumSet, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, java.util.Set, java.util.List, boolean, java.lang.Integer, java.lang.Integer, java.util.List, java.util.Map) + */ + @Override + public Pager> findTaxaAndNamesByFullText( + EnumSet searchModes, String queryString, Classification classification, + Set namedAreas, Set> distributionStatus, List languages, + boolean highlightFragments, Integer pageSize, + Integer pageNumber, List orderHints, List propertyPaths) + throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException { + + // FIXME: allow taxonomic ordering + // hql equivalent: order by t.name.genusOrUninomial, case when t.name.specificEpithet like '\"%\"' then 1 else 0 end, t.name.specificEpithet, t.name.rank desc, t.name.nameCache"; + // this require building a special sort column by a special classBridge + if(highlightFragments){ + logger.warn("findTaxaAndNamesByFullText() : fragment highlighting is " + + "currently not fully supported by this method and thus " + + "may not work with common names and misapplied names."); + } + + // convert sets to lists + List namedAreaList = null; + List>distributionStatusList = null; + if(namedAreas != null){ + namedAreaList = new ArrayList(namedAreas.size()); + namedAreaList.addAll(namedAreas); + } + if(distributionStatus != null){ + distributionStatusList = new ArrayList>(distributionStatus.size()); + distributionStatusList.addAll(distributionStatus); + } + + // set default if parameter is null + if(searchModes == null){ + searchModes = EnumSet.of(TaxaAndNamesSearchMode.doTaxa); + } + + // set sort order and thus override any sort orders which may have been + // defindes by prepare*Search methods + if(orderHints == null){ + orderHints = OrderHint.NOMENCLATURAL_SORT_ORDER; + } + SortField[] sortFields = new SortField[orderHints.size()]; + int i = 0; + for(OrderHint oh : orderHints){ + sortFields[i++] = oh.toSortField(); + } +// SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("id", SortField.STRING, false)}; +// SortField[] sortFields = new SortField[]{new SortField(NomenclaturalSortOrderBrigde.NAME_SORT_FIELD_NAME, SortField.STRING, false)}; + + + boolean addDistributionFilter = namedAreas != null && namedAreas.size() > 0; + + List luceneSearches = new ArrayList(); + Map idFieldMap = new HashMap(); + + /* + ======== filtering by distribution , HOWTO ======== + + - http://www.javaranch.com/journal/2009/02/filtering-a-lucene-search.html + - http://stackoverflow.com/questions/17709256/lucene-solr-using-complex-filters -> QueryWrapperFilter + add Filter to search as http://lucene.apache.org/core/3_6_0/api/all/org/apache/lucene/search/Filter.html + which will be put into a FilteredQuersy in the end ? + + + 3. how does it work in spatial? + see + - http://www.nsshutdown.com/projects/lucene/whitepaper/locallucene_v2.html + - http://www.infoq.com/articles/LuceneSpatialSupport + - http://www.mhaller.de/archives/156-Spatial-search-with-Lucene.html + ------------------------------------------------------------------------ + + filter strategies: + A) use a separate distribution filter per index sub-query/search: + - byTaxonSyonym (query TaxaonBase): + use a join area filter (Distribution -> TaxonBase) + - byCommonName (query DescriptionElementBase): use an area filter on + DescriptionElementBase !!! PROBLEM !!! + This cannot work since the distributions are different entities than the + common names and thus these are different lucene documents. + - byMisaplliedNames (join query TaxonRelationship -> TaxaonBase): + use a join area filter (Distribution -> TaxonBase) + + B) use a common distribution filter for all index sub-query/searches: + - use a common join area filter (Distribution -> TaxonBase) + - also implement the byCommonName as join query (CommonName -> TaxonBase) + PROBLEM in this case: we are losing the fragment highlighting for the + common names, since the returned documents are always TaxonBases + */ + + /* The QueryFactory for creating filter queries on Distributions should + * The query factory used for the common names query cannot be reused + * for this case, since we want to only record the text fields which are + * actually used in the primary query + */ + QueryFactory distributionFilterQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Distribution.class); + + BooleanFilter multiIndexByAreaFilter = new BooleanFilter(); + + + // search for taxa or synonyms + if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) || searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) { + Class taxonBaseSubclass = TaxonBase.class; + if(searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && !searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){ + taxonBaseSubclass = Taxon.class; + } else if (!searchModes.contains(TaxaAndNamesSearchMode.doTaxa) && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)) { + taxonBaseSubclass = Synonym.class; + } + luceneSearches.add(prepareFindByFullTextSearch(taxonBaseSubclass, queryString, classification, languages, highlightFragments, sortFields)); + idFieldMap.put(CdmBaseType.TAXON, "id"); + /* A) does not work!!!! + if(addDistributionFilter){ + // in this case we need a filter which uses a join query + // to get the TaxonBase documents for the DescriptionElementBase documents + // which are matching the areas in question + Query taxonAreaJoinQuery = createByDistributionJoinQuery( + namedAreaList, + distributionStatusList, + distributionFilterQueryFactory + ); + multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD); + } + */ + if(addDistributionFilter && searchModes.contains(TaxaAndNamesSearchMode.doSynonyms)){ + // add additional area filter for synonyms + String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index + String toField = "accTaxon.id"; // id in TaxonBase index + + BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory); + + Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class); + multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD); + + } + } + + // search by CommonTaxonName + if(searchModes.contains(TaxaAndNamesSearchMode.doTaxaByCommonNames)) { + // B) + QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class); + Query byCommonNameJoinQuery = descriptionElementQueryFactory.newJoinQuery( + "inDescription.taxon.id", + "id", + QueryFactory.addTypeRestriction( + createByDescriptionElementFullTextQuery(queryString, classification, null, languages, descriptionElementQueryFactory) + , CommonTaxonName.class + ), + CommonTaxonName.class); + logger.debug("byCommonNameJoinQuery: " + byCommonNameJoinQuery.toString()); + LuceneSearch byCommonNameSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class); + byCommonNameSearch.setCdmTypRestriction(Taxon.class); + byCommonNameSearch.setQuery(byCommonNameJoinQuery); + byCommonNameSearch.setSortFields(sortFields); + idFieldMap.put(CdmBaseType.TAXON, "id"); + + luceneSearches.add(byCommonNameSearch); + + /* A) does not work!!!! + luceneSearches.add( + prepareByDescriptionElementFullTextSearch(CommonTaxonName.class, + queryString, classification, null, languages, highlightFragments) + ); + idFieldMap.put(CdmBaseType.DESCRIPTION_ELEMENT, "inDescription.taxon.id"); + if(addDistributionFilter){ + // in this case we are able to use DescriptionElementBase documents + // which are matching the areas in question directly + BooleanQuery byDistributionQuery = createByDistributionQuery( + namedAreaList, + distributionStatusList, + distributionFilterQueryFactory + ); + multiIndexByAreaFilter.add(new QueryWrapperFilter(byDistributionQuery), Occur.SHOULD); + } */ + } + + // search by misapplied names + if(searchModes.contains(TaxaAndNamesSearchMode.doMisappliedNames)) { + // NOTE: + // prepareFindByTaxonRelationFullTextSearch() is making use of JoinUtil.createJoinQuery() + // which allows doing query time joins + // finds the misapplied name (Taxon B) which is an misapplication for + // a related Taxon A. + // + luceneSearches.add(prepareFindByTaxonRelationFullTextSearch( + new TaxonRelationshipEdge(TaxonRelationshipType.MISAPPLIED_NAME_FOR(), Direction.relatedTo), + queryString, classification, languages, highlightFragments, sortFields)); + idFieldMap.put(CdmBaseType.TAXON, "id"); + + if(addDistributionFilter){ + String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index + + /* + * Here i was facing wired and nasty bug which took me bugging be really for hours until I found this solution. + * Maybe this is a but in java itself java. + * + * When the string toField is constructed by using the expression TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() + * directly: + * + * String toField = "relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id"; + * + * The byDistributionQuery fails, however when the uuid is first stored in another string variable the query + * will execute as expected: + * + * String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString(); + * String toField = "relation." + misappliedNameForUuid +".to.id"; + * + * Comparing both strings by the String.equals method returns true, so both String are identical. + * + * The bug occurs when running eu.etaxonomy.cdm.api.service.TaxonServiceSearchTest in eclipse and in maven and seems to to be + * dependent from a specific jvm (openjdk6 6b27-1.12.6-1ubuntu0.13.04.2, openjdk7 7u25-2.3.10-1ubuntu0.13.04.2, oracle jdk1.7.0_25 tested) + * The bug is persistent after a reboot of the development computer. + */ +// String misappliedNameForUuid = TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString(); +// String toField = "relation." + misappliedNameForUuid +".to.id"; + String toField = "relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id"; +// System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + misappliedNameForUuid +".to.id") ? " > identical" : " > different"); +// System.out.println("relation.1ed87175-59dd-437e-959e-0d71583d8417.to.id".equals("relation." + TaxonRelationshipType.MISAPPLIED_NAME_FOR().getUuid().toString() +".to.id") ? " > identical" : " > different"); + + BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, distributionFilterQueryFactory); + Query taxonAreaJoinQuery = distributionFilterQueryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class); + QueryWrapperFilter filter = new QueryWrapperFilter(taxonAreaJoinQuery); + +// debug code for bug described above + DocIdSet filterMatchSet = filter.getDocIdSet(luceneIndexToolProvider.getIndexReaderFor(Taxon.class)); +// System.err.println(DocIdBitSetPrinter.docsAsString(filterMatchSet, 100)); + + multiIndexByAreaFilter.add(filter, Occur.SHOULD); + } + } + + LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, + luceneSearches.toArray(new LuceneSearch[luceneSearches.size()])); + + + if(addDistributionFilter){ + + // B) + // in this case we need a filter which uses a join query + // to get the TaxonBase documents for the DescriptionElementBase documents + // which are matching the areas in question + // + // for toTaxa, doByCommonName + Query taxonAreaJoinQuery = createByDistributionJoinQuery( + namedAreaList, + distributionStatusList, + distributionFilterQueryFactory + ); + multiIndexByAreaFilter.add(new QueryWrapperFilter(taxonAreaJoinQuery), Occur.SHOULD); + } + + if (addDistributionFilter){ + multiSearch.setFilter(multiIndexByAreaFilter); + } + + + // --- execute search + TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber); + + // --- initialize taxa, highlight matches .... + ISearchResultBuilder searchResultBuilder = new SearchResultBuilder(multiSearch, multiSearch.getQuery()); + + + List> searchResults = searchResultBuilder.createResultSet( + topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths); + + int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0; + return new DefaultPagerImpl>(pageNumber, totalHits, pageSize, searchResults); + } + + /** + * @param namedAreaList at least one area must be in the list + * @param distributionStatusList optional + * @return + * @throws IOException + */ + protected Query createByDistributionJoinQuery( + List namedAreaList, + List> distributionStatusList, + QueryFactory queryFactory + ) throws IOException { + + String fromField = "inDescription.taxon.id"; // in DescriptionElementBase index + String toField = "id"; // id in TaxonBase index + + BooleanQuery byDistributionQuery = createByDistributionQuery(namedAreaList, distributionStatusList, queryFactory); + + Query taxonAreaJoinQuery = queryFactory.newJoinQuery(fromField, toField, byDistributionQuery, Distribution.class); + + return taxonAreaJoinQuery; + } + + /** + * @param namedAreaList + * @param distributionStatusList + * @param queryFactory + * @return + */ + private BooleanQuery createByDistributionQuery(List namedAreaList, + List> distributionStatusList, QueryFactory queryFactory) { + BooleanQuery areaQuery = new BooleanQuery(); + // area field from Distribution + areaQuery.add(queryFactory.newEntityIdsQuery("area.id", namedAreaList), Occur.MUST); + + // status field from Distribution + if(distributionStatusList != null && distributionStatusList.size() > 0){ + areaQuery.add(queryFactory.newEntityIdsQuery("status.id", distributionStatusList), Occur.MUST); } + + logger.debug("createByDistributionQuery() query: " + areaQuery.toString()); + return areaQuery; + } + + /** + * This method has been primarily created for testing the area join query but might + * also be useful in other situations + * + * @param namedAreaList + * @param distributionStatusList + * @param classification + * @param highlightFragments + * @return + * @throws IOException + */ + protected LuceneSearch prepareByDistributionSearch( + List namedAreaList, List> distributionStatusList, + Classification classification) throws IOException { + + BooleanQuery finalQuery = new BooleanQuery(); + + LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, Taxon.class); + + // FIXME is this query factory using the wrong type? + QueryFactory taxonQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(Taxon.class); + + SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("titleCache__sort", SortField.STRING, false)}; + luceneSearch.setSortFields(sortFields); + + + Query byAreaQuery = createByDistributionJoinQuery(namedAreaList, distributionStatusList, taxonQueryFactory); + + finalQuery.add(byAreaQuery, Occur.MUST); + + if(classification != null){ + finalQuery.add(taxonQueryFactory.newEntityIdQuery("taxonNodes.classification.id", classification), Occur.MUST); + } + + logger.info("prepareByAreaSearch() query: " + finalQuery.toString()); + luceneSearch.setQuery(finalQuery); + return luceneSearch; } + /* (non-Javadoc) * @see eu.etaxonomy.cdm.api.service.ITaxonService#findByDescriptionElementFullText(java.lang.Class, java.lang.String, eu.etaxonomy.cdm.model.taxon.Classification, java.util.List, java.util.List, boolean, java.lang.Integer, java.lang.Integer, java.util.List, java.util.List) */ @@ -1491,12 +2188,12 @@ public class TaxonServiceImpl extends IdentifiableServiceBase> findByEverythingFullText(String queryString, Classification classification, List languages, boolean highlightFragments, - Integer pageSize, Integer pageNumber, List orderHints, List propertyPaths) throws CorruptIndexException, IOException, ParseException { + Integer pageSize, Integer pageNumber, List orderHints, List propertyPaths) throws CorruptIndexException, IOException, ParseException, LuceneMultiSearchException { LuceneSearch luceneSearchByDescriptionElement = prepareByDescriptionElementFullTextSearch(null, queryString, classification, null, languages, highlightFragments); - LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments); + LuceneSearch luceneSearchByTaxonBase = prepareFindByFullTextSearch(null, queryString, classification, languages, highlightFragments, null); - LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneSearchByDescriptionElement, luceneSearchByTaxonBase); + LuceneMultiSearch multiSearch = new LuceneMultiSearch(luceneIndexToolProvider, luceneSearchByDescriptionElement, luceneSearchByTaxonBase); // --- execute search TopGroupsWithMaxScore topDocsResultSet = multiSearch.executeSearch(pageSize, pageNumber); @@ -1511,7 +2208,7 @@ public class TaxonServiceImpl extends IdentifiableServiceBase> searchResults = searchResultBuilder.createResultSet( topDocsResultSet, multiSearch.getHighlightFields(), dao, idFieldMap, propertyPaths); - int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupedHitCount : 0; + int totalHits = topDocsResultSet != null ? topDocsResultSet.topGroups.totalGroupCount : 0; return new DefaultPagerImpl>(pageNumber, totalHits, pageSize, searchResults); } @@ -1527,75 +2224,91 @@ public class TaxonServiceImpl extends IdentifiableServiceBase clazz, String queryString, Classification classification, List features, + protected LuceneSearch prepareByDescriptionElementFullTextSearch(Class clazz, + String queryString, Classification classification, List features, List languages, boolean highlightFragments) { - BooleanQuery finalQuery = new BooleanQuery(); - BooleanQuery textQuery = new BooleanQuery(); - LuceneSearch luceneSearch = new LuceneSearch(getSession(), GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class); - QueryFactory queryFactory = new QueryFactory(luceneSearch); + LuceneSearch luceneSearch = new LuceneSearch(luceneIndexToolProvider, GroupByTaxonClassBridge.GROUPBY_TAXON_FIELD, DescriptionElementBase.class); + QueryFactory descriptionElementQueryFactory = luceneIndexToolProvider.newQueryFactoryFor(DescriptionElementBase.class); SortField[] sortFields = new SortField[]{SortField.FIELD_SCORE, new SortField("inDescription.taxon.titleCache__sort", SortField.STRING, false)}; + + BooleanQuery finalQuery = createByDescriptionElementFullTextQuery(queryString, classification, features, + languages, descriptionElementQueryFactory); + luceneSearch.setSortFields(sortFields); + luceneSearch.setCdmTypRestriction(clazz); + luceneSearch.setQuery(finalQuery); + if(highlightFragments){ + luceneSearch.setHighlightFields(descriptionElementQueryFactory.getTextFieldNamesAsArray()); + } - // ---- search criteria - luceneSearch.setClazz(clazz); - textQuery.add(queryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD); + return luceneSearch; + } + + /** + * @param queryString + * @param classification + * @param features + * @param languages + * @param descriptionElementQueryFactory + * @return + */ + private BooleanQuery createByDescriptionElementFullTextQuery(String queryString, Classification classification, + List features, List languages, QueryFactory descriptionElementQueryFactory) { + BooleanQuery finalQuery = new BooleanQuery(); + BooleanQuery textQuery = new BooleanQuery(); + textQuery.add(descriptionElementQueryFactory.newTermQuery("titleCache", queryString), Occur.SHOULD); // common name Query nameQuery; if(languages == null || languages.size() == 0){ - nameQuery = queryFactory.newTermQuery("name", queryString); + nameQuery = descriptionElementQueryFactory.newTermQuery("name", queryString); } else { nameQuery = new BooleanQuery(); BooleanQuery languageSubQuery = new BooleanQuery(); for(Language lang : languages){ - languageSubQuery.add(queryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD); + languageSubQuery.add(descriptionElementQueryFactory.newTermQuery("language.uuid", lang.getUuid().toString(), false), Occur.SHOULD); } - ((BooleanQuery) nameQuery).add(queryFactory.newTermQuery("name", queryString), Occur.MUST); + ((BooleanQuery) nameQuery).add(descriptionElementQueryFactory.newTermQuery("name", queryString), Occur.MUST); ((BooleanQuery) nameQuery).add(languageSubQuery, Occur.MUST); } textQuery.add(nameQuery, Occur.SHOULD); // text field from TextData - textQuery.add(queryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD); + textQuery.add(descriptionElementQueryFactory.newMultilanguageTextQuery("text", queryString, languages), Occur.SHOULD); // --- TermBase fields - by representation ---- // state field from CategoricalData - textQuery.add(queryFactory.newDefinedTermQuery("states.state", queryString, languages), Occur.SHOULD); + textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.state", queryString, languages), Occur.SHOULD); // state field from CategoricalData - textQuery.add(queryFactory.newDefinedTermQuery("states.modifyingText", queryString, languages), Occur.SHOULD); + textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("stateData.modifyingText", queryString, languages), Occur.SHOULD); // area field from Distribution - textQuery.add(queryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD); + textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("area", queryString, languages), Occur.SHOULD); // status field from Distribution - textQuery.add(queryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD); + textQuery.add(descriptionElementQueryFactory.newDefinedTermQuery("status", queryString, languages), Occur.SHOULD); finalQuery.add(textQuery, Occur.MUST); // --- classification ---- if(classification != null){ - finalQuery.add(queryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST); + finalQuery.add(descriptionElementQueryFactory.newEntityIdQuery("inDescription.taxon.taxonNodes.classification.id", classification), Occur.MUST); } // --- IdentifieableEntity fields - by uuid if(features != null && features.size() > 0 ){ - finalQuery.add(queryFactory.newEntityUuidQuery("feature.uuid", features), Occur.MUST); + finalQuery.add(descriptionElementQueryFactory.newEntityUuidsQuery("feature.uuid", features), Occur.MUST); } // the description must be associated with a taxon - finalQuery.add(queryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST); + finalQuery.add(descriptionElementQueryFactory.newIsNotNullQuery("inDescription.taxon.id"), Occur.MUST); logger.info("prepareByDescriptionElementFullTextSearch() query: " + finalQuery.toString()); - luceneSearch.setQuery(finalQuery); - - if(highlightFragments){ - luceneSearch.setHighlightFields(queryFactory.getTextFieldNamesAsArray()); - } - return luceneSearch; + return finalQuery; } /** @@ -1648,7 +2361,7 @@ public class TaxonServiceImpl extends IdentifiableServiceBase nodes = taxon.getTaxonNodes(); - List taxonNames = new ArrayList(); + List taxonNames = new ArrayList(); for (TaxonNode node: nodes){ // HashMap synonymsGenus = new HashMap(); // Changed this to be able to store the idInSource to a genusName @@ -1995,9 +2708,9 @@ public class TaxonServiceImpl extends IdentifiableServiceBase sourceReference = syn.getSec(); if (sourceReference == null){ - logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon"); - //TODO:Remove - System.out.println("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec()); - sourceReference = taxon.getSec(); + logger.warn("The synonym has no sec reference because it is a misapplied name! Take the sec reference of taxon" + taxon.getSec()); + sourceReference = taxon.getSec(); } synName = syn.getName(); @@ -2162,11 +2877,13 @@ public class TaxonServiceImpl extends IdentifiableServiceBase synonymName = fromTaxon.getName(); + Synonym synonym = Synonym.NewInstance(synonymName, fromTaxon.getSec()); + + // Remove concept relation from taxon + toTaxon.removeTaxon(fromTaxon, oldRelationshipType); + + + + // Create a new synonym for the taxon + SynonymRelationship synonymRelationship; + if (synonymRelationshipType != null + && synonymRelationshipType.equals(SynonymRelationshipType.HOMOTYPIC_SYNONYM_OF())){ + synonymRelationship = toTaxon.addHomotypicSynonym(synonym, null, null); + } else{ + synonymRelationship = toTaxon.addHeterotypicSynonymName(synonymName); + } + + this.saveOrUpdate(toTaxon); + //TODO: configurator and classification + this.deleteTaxon(fromTaxon, null, null); + return synonymRelationship.getSynonym(); + + } + @Override + public List isDeletable(TaxonBase taxonBase, DeleteConfiguratorBase config){ + List result = new ArrayList(); + Set references = commonService.getReferencingObjects(taxonBase); + if (taxonBase instanceof Taxon){ + TaxonDeletionConfigurator taxonConfig = (TaxonDeletionConfigurator) config; + result = isDeletableForTaxon(references, taxonConfig); + }else{ + SynonymDeletionConfigurator synonymConfig = (SynonymDeletionConfigurator) config; + result = isDeletableForSynonym(references, synonymConfig); + } + return result; + } + + private List isDeletableForSynonym(Set references, SynonymDeletionConfigurator config){ + String message; + List result = new ArrayList(); + for (CdmBase ref: references){ + if (!(ref instanceof SynonymRelationship || ref instanceof Taxon || ref instanceof TaxonNameBase)){ + message = "The Synonym can't be deleted as long as it is referenced by " + ref.getClass().getSimpleName() + " with id "+ ref.getId(); + result.add(message); + } + } + + return result; + } + private List isDeletableForTaxon(Set references, TaxonDeletionConfigurator config){ + String message; + List result = new ArrayList(); + for (CdmBase ref: references){ + if (!(ref instanceof TaxonNameBase)){ + if (!config.isDeleteSynonymRelations() && (ref instanceof SynonymRelationship)){ + message = "The Taxon can't be deleted as long as it has synonyms."; + result.add(message); + } + if (!config.isDeleteDescriptions() && (ref instanceof DescriptionBase)){ + message = "The Taxon can't be deleted as long as it has factual data."; + result.add(message); + } + + if (!config.isDeleteTaxonNodes() && (ref instanceof TaxonNode)){ + message = "The Taxon can't be deleted as long as it belongs to a taxon node."; + result.add(message); + } + if (!config.isDeleteTaxonRelationships() && (ref instanceof TaxonNode)){ + if (!config.isDeleteMisappliedNamesAndInvalidDesignations() && (((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.MISAPPLIED_NAME_FOR())|| ((TaxonRelationship)ref).getType().equals(TaxonRelationshipType.INVALID_DESIGNATION_FOR()))){ + message = "The Taxon can't be deleted as long as it has misapplied names or invalid designations."; + result.add(message); + } else{ + message = "The Taxon can't be deleted as long as it belongs to a taxon node."; + result.add(message); + } + } + if (ref instanceof PolytomousKeyNode){ + message = "The Taxon can't be deleted as long as it is referenced by a polytomous key node."; + result.add(message); + } + + if (HibernateProxyHelper.isInstanceOf(ref, IIdentificationKey.class)){ + message = "Taxon can't be deleted as it is used in an identification key. Remove from identification key prior to deleting this name"; + result.add(message); + + } + + + /* //PolytomousKeyNode + if (referencingObject.isInstanceOf(PolytomousKeyNode.class)){ + String message = "Taxon" + taxon.getTitleCache() + " can't be deleted as it is used in polytomous key node"; + return message; + }*/ + + //TaxonInteraction + if (ref.isInstanceOf(TaxonInteraction.class)){ + message = "Taxon can't be deleted as it is used in taxonInteraction#taxon2"; + result.add(message); + } + + //TaxonInteraction + if (ref.isInstanceOf(DeterminationEvent.class)){ + message = "Taxon can't be deleted as it is used in a determination event"; + result.add(message); + } + + } + + } + + return result; + } -} + }