edu.unc.lib.dl.util.VocabularyHelperManager.java Source code

Java tutorial

Introduction

Here is the source code for edu.unc.lib.dl.util.VocabularyHelperManager.java

Source

/**
 * Copyright 2008 The University of North Carolina at Chapel Hill
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package edu.unc.lib.dl.util;

import static edu.unc.lib.dl.util.ContentModelHelper.CDRProperty.replaceInvalidTerms;
import static edu.unc.lib.dl.util.ContentModelHelper.Model.COLLECTION;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import edu.unc.lib.dl.fedora.AccessClient;
import edu.unc.lib.dl.fedora.FedoraException;
import edu.unc.lib.dl.fedora.ManagementClient;
import edu.unc.lib.dl.fedora.PID;
import edu.unc.lib.dl.util.ContentModelHelper.CDRProperty;
import edu.unc.lib.dl.xml.VocabularyHelper;

/**
 * Manages and initializes a set of vocabulary helper classes from a mapping of vocabulary types to helper classes.
 * Associates configuration of vocabularies with the original vocabulary content and application levels for specific
 * collections in the repository. Provides helper methods for determining which helpers are applicable and for
 * performing operations over all applicable helpers.
 *
 * @author bbpennel
 * @date Sep 30, 2014
 */
public class VocabularyHelperManager {

    private static final Logger log = LoggerFactory.getLogger(VocabularyHelperManager.class);

    // Map of vocabulary type to helper classes
    private Map<String, Class<?>> helperClassMap;

    // Map of vocabulary type to helper objects
    private Map<String, VocabularyHelper> vocabHelperMap;

    // Map of collection pids to applicable vocabularies
    private Map<String, Set<VocabularyHelper>> pidToHelpers;

    // Map of vocabulary uris to fedora objects those keys apply to
    private Map<String, List<String>> vocabURIToPID;

    // Map of bound pids to application type per vocabulary
    private Map<String, Map<String, Set<String>>> pidToVocabApplication;

    // Configuration info per vocabulary, uri to properties map
    private Map<String, Map<String, String>> vocabInfoMap;

    private PID collectionsPID;

    @Autowired
    private TripleStoreQueryService queryService;
    @Autowired
    private ManagementClient managementClient;
    @Autowired
    private AccessClient accessClient;

    private Boolean initialized = false;

    public synchronized void init() {
        log.debug("Initializing vocabulary helpers");
        initialized = true;

        // Scan fedora for objects with the vocabulary content model
        vocabInfoMap = queryService.fetchVocabularyInfo();

        pidToVocabApplication = queryService.fetchVocabularyMapping();

        // Instantiate helper classes per vocabulary definition
        instantiateVocabularyHelpers();

        // Establish the associations between containers and vocabularies
        linkHelpersToPIDs();

        // Load the contents of the vocabularies
        for (String vocabPID : vocabInfoMap.keySet()) {
            try {
                byte[] stream = accessClient.getDatastreamDissemination(new PID(vocabPID),
                        ContentModelHelper.Datastream.DATA_FILE.getName(), null).getStream();

                if (vocabInfoMap.containsKey(vocabPID)) {
                    String vocabURI = vocabInfoMap.get(vocabPID).get("vocabURI");
                    vocabHelperMap.get(vocabURI).setContent(stream);
                }
            } catch (Exception e) {
                log.error("Failed to load vocabulary content for {}", vocabPID, e);
            }
        }

        if (log.isDebugEnabled()) {
            for (Entry<String, Map<String, Set<String>>> pidEntry : pidToVocabApplication.entrySet()) {
                log.debug("Vocabularies bound to {}", pidEntry.getKey());
                for (Entry<String, Set<String>> vocabEntry : pidEntry.getValue().entrySet()) {
                    log.debug("  {}: {}", vocabEntry.getKey(), vocabEntry.getValue());
                }
            }
        }
    }

    private void instantiateVocabularyHelpers() {
        vocabHelperMap = new HashMap<>();

        for (Map<String, String> info : vocabInfoMap.values()) {
            try {
                String vocabType = info.get("vocabType");
                String vocabURI = info.get("vocabURI");
                if (!helperClassMap.containsKey(vocabType)) {
                    log.warn("Vocabulary {} specifies a vocabulary type that does not map to a helper class",
                            vocabURI, vocabType);
                    continue;
                }

                VocabularyHelper helperObject = (VocabularyHelper) helperClassMap.get(vocabType).getConstructor()
                        .newInstance();

                helperObject.setVocabularyURI(vocabURI);
                helperObject.setSelector(info.get("vocabularySelector"));

                vocabHelperMap.put(vocabURI, helperObject);
            } catch (Exception e) {
                log.error("Failed to instantiate vocabulary helper class for {}", info.get("vocabURI"), e);
            }
        }
    }

    private void linkHelpersToPIDs() {
        if (pidToVocabApplication == null) {
            pidToHelpers = null;
            return;
        }

        pidToHelpers = new HashMap<>();
        vocabURIToPID = new HashMap<>();

        for (Entry<String, Map<String, Set<String>>> entry : this.pidToVocabApplication.entrySet()) {
            String pid = entry.getKey();
            Set<VocabularyHelper> helpers = new HashSet<>();
            pidToHelpers.put(pid, helpers);
            log.debug("Storing pidtohelper {}", pid);
            for (String vocabUri : entry.getValue().keySet()) {
                helpers.add(vocabHelperMap.get(vocabUri));

                List<String> linkedPIDs = vocabURIToPID.get(vocabUri);
                if (linkedPIDs == null) {
                    linkedPIDs = new ArrayList<>();
                    vocabURIToPID.put(vocabUri, linkedPIDs);
                }
                linkedPIDs.add(pid);
                log.debug("Vocab {} has {}", vocabUri, linkedPIDs);
            }
            log.debug("Helpers so far: {}", helpers);
        }
        log.debug("pidtohelpers: {}", pidToHelpers);
    }

    /**
     * Updates an object's invalid term state in Fedora based on any invalid terms found in the given document
     *
     * @param pid
     * @param docElement
     * @throws FedoraException
     */
    public void updateInvalidTermsRelations(PID pid, Element docElement) throws FedoraException {
        Set<VocabularyHelper> helpers = getHelpers(pid);
        if (helpers == null)
            return;

        String invalidTermPred = CDRProperty.invalidTerm.toString();
        List<String> allExistingTerms = queryService.fetchBySubjectAndPredicate(pid, invalidTermPred);

        // Decompose triple values and group terms by vocabulary prefix
        Map<String, List<String>> termMap = new HashMap<>();
        for (String term : allExistingTerms) {
            String parts[] = term.split("\\|", 2);

            List<String> terms = termMap.get(parts[0]);
            if (terms == null) {
                terms = new ArrayList<>();
                termMap.put(parts[0], terms);
            }

            terms.add(term);
        }

        for (VocabularyHelper helper : helpers) {
            List<String> existingTerms = termMap.get(helper.getInvalidTermPrefix());

            Set<String> invalidTerms;
            try {
                invalidTerms = helper.getInvalidTermsWithPrefix(docElement);
            } catch (JDOMException e) {
                log.error("Failed to extract invalid terms from {}", pid.getPid(), e);
                continue;
            }

            if (existingTerms != null && invalidTerms.size() == existingTerms.size()
                    && invalidTerms.containsAll(existingTerms)) {
                continue;
            }

            if (existingTerms != null) {
                // Remove any terms which are no longer present
                List<String> removeTerms = new ArrayList<String>(existingTerms);
                removeTerms.removeAll(invalidTerms);

                for (String term : removeTerms) {
                    managementClient.purgeLiteralStatement(pid, invalidTermPred, term, null);
                }

                // Calculate the set of newly invalid terms which need to be added
                invalidTerms.removeAll(existingTerms);
            }

            if (invalidTerms.size() > 0) {
                for (String term : invalidTerms) {
                    managementClient.addLiteralStatement(pid, invalidTermPred, term, null);
                }
            }
        }
    }

    /**
     * Returns a map of invalid terms per vocabulary URI
     *
     * @param pid
     * @param docElement
     * @return
     */
    public Map<String, Set<String>> getInvalidTerms(PID pid, Element docElement) {
        return getInvalidTerms(pid, docElement, false);
    }

    public Map<String, Set<String>> getInvalidTermsWithPrefix(PID pid, Element docElement) {
        return getInvalidTerms(pid, docElement, true);
    }

    private Map<String, Set<String>> getInvalidTerms(PID pid, Element docElement, boolean includePrefix) {

        Set<VocabularyHelper> helpers = getHelpers(pid);
        if (helpers == null)
            return null;

        Map<String, Set<String>> results = new LinkedHashMap<>();

        for (VocabularyHelper helper : helpers) {
            if (helper != null) {
                try {
                    Set<String> invalidTerms;
                    if (includePrefix) {
                        invalidTerms = helper.getInvalidTermsWithPrefix(docElement);
                    } else {
                        invalidTerms = helper.getInvalidTerms(docElement);
                    }

                    if (invalidTerms != null)
                        results.put(helper.getVocabularyURI(), invalidTerms);
                } catch (JDOMException e) {
                    log.error("Failed to get invalid vocabulary terms of {}", pid, e);
                }
            }
        }

        return results;
    }

    /**
     * Get the invalid term predicate for the given vocabulary
     *
     * @param vocabKey
     * @return
     */
    public String getInvalidTermPrefix(String vocabKey) {
        VocabularyHelper helper = vocabHelperMap.get(vocabKey);
        if (helper == null)
            return null;
        return helper.getInvalidTermPrefix();
    }

    /**
     * Returns the set of vocabulary helpers that apply to the provided pid, as defined on its parent collection
     *
     * @param pid
     * @return
     */
    public Set<VocabularyHelper> getHelpers(PID pid) {
        return getHelpers(pid, null);
    }

    /**
     * Returns all helpers for the given pid which have invalid term remapping enabled
     *
     * @param pid
     * @param appLevel
     * @return
     */
    public Set<VocabularyHelper> getRemappingHelpers(PID pid) {
        return getHelpers(pid, replaceInvalidTerms);
    }

    private Set<VocabularyHelper> getHelpers(PID pid, CDRProperty appLevel) {
        synchronized (initialized) {
            if (!initialized)
                init();
        }

        PID parentCollectionPID = queryService.fetchParentCollection(pid);
        if (parentCollectionPID == null) {
            List<URI> models = queryService.lookupContentModels(pid);
            if (models.contains(COLLECTION.getURI())) {
                parentCollectionPID = pid;
            }
        }

        // Start helpers from the set of globals assigned to the collections object
        Set<VocabularyHelper> helpers = new HashSet<>();
        Set<VocabularyHelper> rootHelpers = pidToHelpers.get(collectionsPID.getURI());
        if (rootHelpers != null)
            helpers.addAll(rootHelpers);

        if (parentCollectionPID != null) {
            Set<VocabularyHelper> parentHelpers = pidToHelpers.get(parentCollectionPID.getURI());
            if (parentHelpers != null) {
                helpers.addAll(parentHelpers);
            }
        }

        if (appLevel != null && helpers != null)
            filterHelperSet(parentCollectionPID, helpers, appLevel);

        if (log.isDebugEnabled())
            log.debug("Helpers found for {} with {}: {}", new Object[] { pid, appLevel, helpers });

        return helpers;
    }

    private void filterHelperSet(PID pid, Set<VocabularyHelper> helpers, CDRProperty appLevel) {
        if (helpers == null)
            return;

        Map<String, Set<String>> appLevelMap = pidToVocabApplication.get(collectionsPID.getURI());
        if (pid != null) {
            Map<String, Set<String>> collectionLevel = pidToVocabApplication.get(pid.getURI());
            if (collectionLevel != null) {
                if (appLevelMap == null) {
                    appLevelMap = collectionLevel;
                } else {
                    appLevelMap.putAll(collectionLevel);
                }
            }
        }

        if (appLevelMap == null)
            return;

        Iterator<VocabularyHelper> helperIt = helpers.iterator();
        while (helperIt.hasNext()) {
            VocabularyHelper helper = helperIt.next();

            Set<String> appLevels = appLevelMap.get(helper.getVocabularyURI());
            if (!appLevels.contains(appLevel.toString())) {
                helperIt.remove();
            }
        }
    }

    public VocabularyHelper getHelper(String vocabURI) {
        return this.vocabHelperMap.get(vocabURI);
    }

    /**
     * Sets the map of helper classes and generates the mapping of vocabulary types to instances of helpers
     *
     * @param helperClasses
     */
    public void setHelperClasses(Map<String, String> helperClasses) {
        this.helperClassMap = new HashMap<>(helperClasses.size());

        for (Entry<String, String> helperEntry : helperClasses.entrySet()) {
            try {
                this.helperClassMap.put(helperEntry.getKey(), Class.forName(helperEntry.getValue()));
            } catch (ClassNotFoundException e) {
                log.error("Failed to get class for helper {}", helperEntry.getValue(), e);
            }
        }
    }

    /**
     * Returns the authoritative vocabulary forms for selected fields from the given document using all applicable
     * helpers for the specified object. Terms are grouped by vocabulary URI.
     *
     * @param pid
     * @param doc
     * @return
     */
    public Map<String, List<List<String>>> getAuthoritativeForms(PID pid, Document doc) {
        return getAuthoritativeForms(pid, doc.getRootElement());
    }

    public Map<String, List<List<String>>> getAuthoritativeForms(PID pid, Element docElement) {
        Set<VocabularyHelper> helpers = getHelpers(pid);
        if (helpers == null)
            return null;

        Map<String, List<List<String>>> results = new HashMap<>();
        for (VocabularyHelper helper : helpers) {
            try {
                List<List<String>> terms = helper.getAuthoritativeForms(docElement);
                if (terms != null && terms.size() > 0)
                    results.put(helper.getVocabularyURI(), terms);
            } catch (JDOMException e) {
                log.error("Failed to get authoritative forms for vocabulary {} on object {}",
                        new Object[] { helper.getVocabularyURI(), pid.getPid(), e });
            }
        }

        return results;
    }

    public void setCollectionsPID(PID collectionsPID) {
        this.collectionsPID = collectionsPID;
    }
}