fr.mcc.ginco.services.ConceptHierarchicalRelationshipServiceUtil.java Source code

Java tutorial

Introduction

Here is the source code for fr.mcc.ginco.services.ConceptHierarchicalRelationshipServiceUtil.java

Source

/**
 * Copyright or  or Copr. Ministre Franais charg de la Culture
 * et de la Communication (2013)
 * <p/>
 * contact.gincoculture_at_gouv.fr
 * <p/>
 * This software is a computer program whose purpose is to provide a thesaurus
 * management solution.
 * <p/>
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software. You can use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 * <p/>
 * As a counterpart to the access to the source code and rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty and the software's author, the holder of the
 * economic rights, and the successive licensors have only limited liability.
 * <p/>
 * In this respect, the user's attention is drawn to the risks associated
 * with loading, using, modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean that it is complicated to manipulate, and that also
 * therefore means that it is reserved for developers and experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systemsand/or
 * data to be ensured and, more generally, to use and operate it in the
 * same conditions as regards security.
 * <p/>
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 */
package fr.mcc.ginco.services;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.collections.ListUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import fr.mcc.ginco.beans.ConceptHierarchicalRelationship;
import fr.mcc.ginco.beans.ThesaurusArrayConcept;
import fr.mcc.ginco.beans.ThesaurusConcept;
import fr.mcc.ginco.dao.IConceptHierarchicalRelationshipDAO;
import fr.mcc.ginco.dao.IThesaurusArrayConceptDAO;
import fr.mcc.ginco.dao.IThesaurusConceptDAO;
import fr.mcc.ginco.dao.IThesaurusTermDAO;
import fr.mcc.ginco.exceptions.BusinessException;
import fr.mcc.ginco.utils.ThesaurusConceptUtils;

@Transactional(readOnly = true, rollbackFor = BusinessException.class)
@Service("conceptHierarchicalRelationshipServiceUtil")
public class ConceptHierarchicalRelationshipServiceUtil implements IConceptHierarchicalRelationshipServiceUtil {

    private static Logger logger = LoggerFactory.getLogger(ConceptHierarchicalRelationshipServiceUtil.class);

    @Inject
    private IThesaurusArrayConceptDAO thesaurusArrayConceptDAO;

    @Inject
    private IThesaurusConceptDAO thesaurusConceptDAO;

    @Inject
    private IThesaurusTermDAO thesaurusTermDAO;

    @Inject
    private IConceptHierarchicalRelationshipDAO conceptHierarchicalRelationshipDAO;

    @Override
    public ThesaurusConcept saveHierarchicalRelationship(ThesaurusConcept conceptToUpdate,
            List<ConceptHierarchicalRelationship> hierarchicalRelationships,
            List<ThesaurusConcept> allRecursiveParents, List<ThesaurusConcept> allRecursiveChilds,
            List<ThesaurusConcept> childrenConceptToDetach, List<ThesaurusConcept> childrenConceptToAttach) {

        // We update the modified relations, and we delete the relations that
        // have been removed
        List<String> oldParentConceptIds = new ArrayList<String>();
        if (!conceptToUpdate.getParentConcepts().isEmpty()) {
            oldParentConceptIds = ThesaurusConceptUtils
                    .getIdsFromConceptList(new ArrayList<ThesaurusConcept>(conceptToUpdate.getParentConcepts()));
        }

        List<String> newParentConceptIds = new ArrayList<String>();
        for (ConceptHierarchicalRelationship relation : hierarchicalRelationships) {
            newParentConceptIds.add(relation.getIdentifier().getParentconceptid());
        }

        List<String> newChildConceptIds = new ArrayList<String>();
        for (ThesaurusConcept newChild : childrenConceptToAttach) {
            newChildConceptIds.add(newChild.getIdentifier());
        }
        // Check loops
        for (ThesaurusConcept childConcept : allRecursiveChilds) {
            if (newParentConceptIds.contains(childConcept.getIdentifier())) {
                throw new BusinessException("A parent concept cannot be the child of the same concept",
                        "hierarchical-loop-violation");
            }
        }
        // Check loops
        for (ThesaurusConcept parentConcept : allRecursiveParents) {
            if (newChildConceptIds.contains(parentConcept.getIdentifier())) {
                throw new BusinessException("A child concept cannot be the parent of the same concept",
                        "hierarchical-loop-violation");
            }
        }

        // Verify if the concept doesn't have one of its brothers as parent
        for (String currentParentId : newParentConceptIds) {
            List<String> childrenOfCurrentParentIds = ThesaurusConceptUtils
                    .getIdsFromConceptList(thesaurusConceptDAO.getChildrenConcepts(currentParentId, 0, null));
            List<String> commonIds = new ArrayList<String>(newParentConceptIds);
            // Compare both lists and see which elements are in common.
            // Those elements are both parents and brothers to the considered concept.
            commonIds.retainAll(childrenOfCurrentParentIds);

            if (!commonIds.isEmpty()) {
                String commonPreferedTerms = "";
                for (String conceptId : commonIds) {
                    if (commonIds.indexOf(conceptId) != 0) {
                        commonPreferedTerms += ", ";
                    }
                    commonPreferedTerms += thesaurusTermDAO.getConceptPreferredTerm(conceptId).getLexicalValue();
                }
                throw new BusinessException(
                        "A concept cannot have one of its brother (" + commonPreferedTerms + ") as a parent",
                        "hierarchical-brotherIsParent-violation", new Object[] { commonPreferedTerms });
            }
        }
        List<String> brotherIds = new ArrayList<String>();
        for (ThesaurusConcept parentConcept : conceptToUpdate.getParentConcepts()) {
            brotherIds.addAll(ThesaurusConceptUtils.getIdsFromConceptList(
                    thesaurusConceptDAO.getChildrenConcepts(parentConcept.getIdentifier(), 0, null)));
        }
        // Verify if the concept doesn't have one of its brothers as child
        for (String currentChildId : newChildConceptIds) {
            if (brotherIds.contains(currentChildId)) {
                List<String> commonIds = new ArrayList<String>(newChildConceptIds);
                String commonPreferedTerms = "";
                for (String conceptId : commonIds) {
                    if (commonIds.indexOf(conceptId) != 0) {
                        commonPreferedTerms += ", ";
                    }
                    commonPreferedTerms += thesaurusTermDAO.getConceptPreferredTerm(conceptId).getLexicalValue();
                }
                throw new BusinessException(
                        "A concept cannot have one of its brother (" + commonPreferedTerms + ") as a parent",
                        "hierarchical-brotherIsParent-violation", new Object[] { commonPreferedTerms });
            }
        }

        List<String> addedParentConceptIds = ListUtils.subtract(newParentConceptIds, oldParentConceptIds);
        List<String> removedParentConceptIds = ListUtils.subtract(oldParentConceptIds, newParentConceptIds);

        List<ThesaurusConcept> addedParentConcepts = new ArrayList<ThesaurusConcept>();
        for (String id : addedParentConceptIds) {
            addedParentConcepts.add(thesaurusConceptDAO.getById(id));
        }

        List<ThesaurusConcept> removedParentConcepts = new ArrayList<ThesaurusConcept>();
        for (String id : removedParentConceptIds) {
            removedParentConcepts.add(thesaurusConceptDAO.getById(id));
        }

        if (!addedParentConcepts.isEmpty() || !removedParentConcepts.isEmpty()) {
            // Treatment in case of modified hierarchy (both add or remove)

            // We remove this concept in all array it belongs
            List<ThesaurusArrayConcept> arrays = thesaurusArrayConceptDAO.getArraysOfConcept(conceptToUpdate);
            for (ThesaurusArrayConcept thesaurusArrayConcept : arrays) {
                thesaurusArrayConceptDAO.delete(thesaurusArrayConcept);
            }

            // We remove all removed parents
            if (!removedParentConcepts.isEmpty()) {
                removeParents(conceptToUpdate, removedParentConcepts);
            }

            // We set all added parents
            Set<ThesaurusConcept> addedParentsSet = new HashSet<ThesaurusConcept>();
            for (ThesaurusConcept addedParentId : addedParentConcepts) {
                addedParentsSet.add(addedParentId);
            }

            if (!addedParentConcepts.isEmpty()) {
                conceptToUpdate.getParentConcepts().addAll(addedParentsSet);
                conceptToUpdate.setTopConcept(false);
            }

            if (!conceptToUpdate.getThesaurus().isPolyHierarchical()
                    && conceptToUpdate.getParentConcepts().size() > 1) {
                throw new BusinessException(
                        "Thesaurus is monohierarchical, but some concepts have multiple parents!",
                        "monohierarchical-violation");
            }

            // We calculate the rootconcepts for the concept to update
            conceptToUpdate.setRootConcepts(new HashSet<ThesaurusConcept>(getRootConcepts(conceptToUpdate)));

            // We launch an async method to calculate new root concept for the
            // children of the concept we update
            calculateChildrenRoots(conceptToUpdate.getIdentifier(), conceptToUpdate.getIdentifier());
        }

        // We process children delete
        addChildren(conceptToUpdate, childrenConceptToAttach);
        removeChildren(conceptToUpdate, childrenConceptToDetach);

        thesaurusConceptDAO.update(conceptToUpdate);
        thesaurusConceptDAO.flush();
        saveRoleOfHierarchicalRelationship(hierarchicalRelationships);

        return conceptToUpdate;
    }

    private void addChildren(ThesaurusConcept conceptToUpdate, List<ThesaurusConcept> childrenConceptToAttach) {
        for (ThesaurusConcept childConcept : childrenConceptToAttach) {
            if (!conceptToUpdate.getThesaurus().isPolyHierarchical()
                    && childConcept.getParentConcepts().size() > 0) {
                throw new BusinessException(
                        "Thesaurus is monohierarchical, but some concepts have multiple parents!",
                        "monohierarchical-violation");
            } else {
                childConcept.getParentConcepts().add(conceptToUpdate);
                thesaurusConceptDAO.update(childConcept);
                thesaurusConceptDAO.flush();
            }
        }
        calculateChildrenRoots(conceptToUpdate.getIdentifier(), conceptToUpdate.getIdentifier());
    }

    public void calculateChildrenRoots(String parentId, String originalParentId) {
        List<ThesaurusConcept> childrenConcepts = thesaurusConceptDAO.getChildrenConcepts(parentId, 0, null);
        for (ThesaurusConcept concept : childrenConcepts) {
            if (concept.getIdentifier() != originalParentId) {
                logger.info("calculating root concept for chiled with concept Id : " + concept.getIdentifier());
                List<ThesaurusConcept> thisChildRoots = getRootConcepts(concept);
                concept.setRootConcepts(new HashSet<ThesaurusConcept>(thisChildRoots));
                calculateChildrenRoots(concept.getIdentifier(), originalParentId);
            }
        }
    }

    @Override
    public List<ThesaurusConcept> getRootConcepts(ThesaurusConcept concept) {
        ThesaurusConcept start;
        Map<String, Integer> path = new HashMap<String, Integer>();
        Set<ThesaurusConcept> roots = new HashSet<ThesaurusConcept>();
        path.clear();
        roots.clear();
        path.put(concept.getIdentifier(), 0);
        start = concept;
        getRoot(concept, 0, start, path, roots);
        return new ArrayList<ThesaurusConcept>(roots);
    }

    private void getRoot(ThesaurusConcept concept, Integer iteration, ThesaurusConcept start,
            Map<String, Integer> path, Set<ThesaurusConcept> roots) {
        iteration++;
        Set<ThesaurusConcept> directParents = concept.getParentConcepts();
        if (directParents.isEmpty()) {
            if (iteration != 1) {
                roots.add(concept);
            }
            return;
        }
        boolean flag = false;
        Set<ThesaurusConcept> stack = new HashSet<ThesaurusConcept>();
        for (ThesaurusConcept directParent : directParents) {
            if (directParent == null || path.containsKey(directParent.getIdentifier())) {
                continue;
            } else {
                path.put(directParent.getIdentifier(), iteration);
                stack.add(directParent);
                flag = true;
            }
        }

        // HACK to deal with cyclic dependencies. Should be reThink in some
        // time...
        if (!flag && directParents.size() == 1 && directParents.contains(start)) {
            roots.add(concept);
        }

        if (!stack.isEmpty()) {
            for (ThesaurusConcept toVisit : stack) {
                getRoot(toVisit, iteration, start, path, roots);
            }
            stack.clear();
        }
    }

    /**
     * This method saves the role of a hierarchical relationship
     *
     * @param hierarchicalRelationships
     * @return
     */
    private List<ConceptHierarchicalRelationship> saveRoleOfHierarchicalRelationship(
            List<ConceptHierarchicalRelationship> hierarchicalRelationships) {
        List<ConceptHierarchicalRelationship> updatedHierarchicalRelationships = new ArrayList<ConceptHierarchicalRelationship>();

        for (ConceptHierarchicalRelationship conceptHierarchicalRelationship : hierarchicalRelationships) {
            ConceptHierarchicalRelationship gettedRelation = conceptHierarchicalRelationshipDAO
                    .getById(conceptHierarchicalRelationship.getIdentifier());
            gettedRelation.setRole(conceptHierarchicalRelationship.getRole());
            updatedHierarchicalRelationships.add(conceptHierarchicalRelationshipDAO.update(gettedRelation));
        }
        return updatedHierarchicalRelationships;
    }

    /**
     * This method detach children concept from concept given in parameter
     *
     * @param conceptToUpdate
     * @param childrenConceptToDetach
     */
    private void removeChildren(ThesaurusConcept conceptToUpdate, List<ThesaurusConcept> childrenConceptToDetach) {
        Set<ThesaurusConcept> parentRootConcepts = conceptToUpdate.getRootConcepts();
        List<ThesaurusConcept> parentToRemove = new ArrayList<ThesaurusConcept>();
        parentToRemove.add(conceptToUpdate);

        for (ThesaurusConcept childConcept : childrenConceptToDetach) {
            removeParents(childConcept, parentToRemove);

            if (conceptToUpdate.getRootConcepts().isEmpty()) {
                // If the parent concept is the root concept, we remove it from
                // root concept of child
                childConcept.getRootConcepts().remove(conceptToUpdate);
            } else {
                // The parent concept is not the root concept, so we remove for
                // each child its root concept
                for (ThesaurusConcept rootConcept : parentRootConcepts) {
                    if (childConcept.getRootConcepts().contains(rootConcept)) {
                        // We remove for children's root concepts all references
                        // to concepts that belongs to
                        childConcept.getRootConcepts().remove(rootConcept);
                    }
                }
            }
            // We recalculate the rootconcepts for children of the child
            // TODO : VERIFIY IF OK IN NON ASYNC LAUNCH
            calculateChildrenRoots(childConcept.getIdentifier(), childConcept.getIdentifier());
        }
    }

    private void removeParents(ThesaurusConcept concept, List<ThesaurusConcept> parents) {
        boolean isDefaultTopConcept = concept.getThesaurus().isDefaultTopConcept();
        if (concept.getParentConcepts().size() == 1) {
            concept.getParentConcepts().clear();
            concept.setTopConcept(isDefaultTopConcept);
        } else {
            for (ThesaurusConcept parent : parents) {
                concept.getParentConcepts().remove(parent);
            }
        }
    }

}