org.phenotips.vocabulary.internal.solr.AbstractSolrVocabularyTerm.java Source code

Java tutorial

Introduction

Here is the source code for org.phenotips.vocabulary.internal.solr.AbstractSolrVocabularyTerm.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 */
package org.phenotips.vocabulary.internal.solr;

import org.phenotips.components.ComponentManagerRegistry;
import org.phenotips.vocabulary.Vocabulary;
import org.phenotips.vocabulary.VocabularyTerm;

import org.xwiki.localization.LocalizationContext;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrDocumentBase;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * Abstract implementation of common functionality of {@link VocabularyTerm} for Solr documents.
 *
 * @version $Id: 258c20f8cf7e0f69ecddcdbbfaeebb5fe9c849b2 $
 * @since 1.3M5
 */
public abstract class AbstractSolrVocabularyTerm implements VocabularyTerm {
    /**
     * The name of the Solr field used for storing the identifier of a term.
     *
     * @see #getId()
     */
    protected static final String ID_KEY = "id";

    /**
     * The name of the Solr field used for storing the name of a term.
     *
     * @see #getName()
     */
    protected static final String NAME_KEY = "name";

    /**
     * The name of the JSON field used for storing the translated name of a term.
     *
     * @see #getTranslatedName()
     */
    protected static final String TRANSLATED_NAME_KEY = "name_translated";

    /**
     * The name of the Solr field used for storing the description of a term.
     *
     * @see #getDescription()
     */
    protected static final String DESCRIPTION_KEY = "def";

    /**
     * The name of the JSON field used for storing the translated description of a term.
     *
     * @see #getTranslatedDescription()
     */
    protected static final String TRANSLATED_DESCRIPTION_KEY = "def_translated";

    /**
     * The name of the Solr field used for storing the ancestors of a term.
     *
     * @see #getAncestors()
     */
    protected static final String ANCESTORS_KEY = "term_category";

    /**
     * The name of the Solr field used for storing the direct parents of a term.
     *
     * @see #getParents()
     */
    protected static final String PARENTS_KEY = "is_a";

    /**
     * The owner vocabulary.
     *
     * @see #getVocabulary()
     */
    protected final Vocabulary vocabulary;

    /** The Solr document representing this term. */
    protected final SolrDocumentBase<? extends Object, ? extends SolrDocumentBase<?, ?>> doc;

    /**
     * The parents of this term, transformed from a set of IDs into a real set of terms.
     *
     * @see #getParents()
     */
    private Set<VocabularyTerm> parents;

    /**
     * The ancestors of this term, transformed from a set of IDs into a real set of terms.
     *
     * @see #getAncestors()
     */
    private Set<VocabularyTerm> ancestors;

    /**
     * A set containing the term itself and its ancestors, transformed from a set of IDs into a real set of terms.
     *
     * @see #getAncestorsAndSelf()
     */
    private Set<VocabularyTerm> ancestorsAndSelf;

    /**
     * Constructor linking to the vocabulary.
     *
     * @param vocabulary the {@link #vocabulary owner vocabulary}
     * @param doc the Solr document holding the actual data
     */
    public AbstractSolrVocabularyTerm(SolrDocumentBase<? extends Object, ? extends SolrDocumentBase<?, ?>> doc,
            Vocabulary vocabulary) {
        this.doc = doc;
        this.vocabulary = vocabulary;
    }

    protected void initialize() {
        if (!isNull()) {
            this.removeSelfFromAncestors();
            this.parents = new LazySolrTermSet(getValues(PARENTS_KEY), this.vocabulary);
            this.ancestors = new LazySolrTermSet(getValues(ANCESTORS_KEY), this.vocabulary);
            this.ancestorsAndSelf = getUncachedAncestorsAndSelf();
        }
    }

    @Override
    public String getId() {
        return (String) getFirstValue(ID_KEY);
    }

    @Override
    public String getName() {
        return (String) getFirstValue(NAME_KEY);
    }

    @Override
    public String getTranslatedName() {
        Collection<?> translated = getTranslatedValues(NAME_KEY);
        if (CollectionUtils.isEmpty(translated)) {
            return null;
        }
        return (String) IterableUtils.get(translated, 0);
    }

    @Override
    public String getDescription() {
        return (String) getFirstValue(DESCRIPTION_KEY);
    }

    @Override
    public String getTranslatedDescription() {
        Collection<?> translated = getTranslatedValues(DESCRIPTION_KEY);
        if (CollectionUtils.isEmpty(translated)) {
            return null;
        }
        return (String) IterableUtils.get(translated, 0);
    }

    @Override
    public Collection<?> getTranslatedValues(String property) {
        Locale currentLocale = getCurrentLocale();
        if (StringUtils.isEmpty(currentLocale.getLanguage())) {
            return getValues(property);
        }
        Collection<Object> result = getValues(property + '_' + currentLocale.toString());
        // If the locale has language, country, and variant, try without the variant
        if (CollectionUtils.isEmpty(result)
                && StringUtils.isNoneEmpty(currentLocale.getVariant(), currentLocale.getCountry())) {
            result = getValues(property + '_' + currentLocale.getLanguage() + '_' + currentLocale.getCountry());
        }
        // If the locale has language and country, try without the country
        if (CollectionUtils.isEmpty(result)
                && StringUtils.isNoneEmpty(currentLocale.getLanguage(), currentLocale.getCountry())) {
            result = getValues(property + '_' + currentLocale.getLanguage());
        }
        // If the locale has no country, then the first call included the language only;
        // at this point, it's certain that no translation is available, return the untranslated default
        if (CollectionUtils.isEmpty(result)) {
            result = getValues(property);
        }
        return result;
    }

    @Override
    public Set<VocabularyTerm> getParents() {
        return this.parents != null ? this.parents : Collections.<VocabularyTerm>emptySet();
    }

    @Override
    public Set<VocabularyTerm> getAncestors() {
        return this.ancestors != null ? this.ancestors : Collections.<VocabularyTerm>emptySet();
    }

    @Override
    public Set<VocabularyTerm> getAncestorsAndSelf() {
        return this.ancestorsAndSelf != null ? this.ancestorsAndSelf : Collections.<VocabularyTerm>emptySet();
    }

    @Override
    public Object get(String key) {
        if (isNull()) {
            return null;
        }
        return this.doc.getFieldValue(key);
    }

    @Override
    public Vocabulary getVocabulary() {
        return this.vocabulary;
    }

    @Override
    public String toString() {
        return "[" + this.getId() + "] " + this.getName();
    }

    @Override
    public long getDistanceTo(final VocabularyTerm other) {
        if (other == null) {
            return -1;
        }
        if (this.equals(other)) {
            return 0;
        }

        long distance = Long.MAX_VALUE;

        Map<String, Integer> myLevelMap = new HashMap<>();
        myLevelMap.put(getId(), 0);
        Map<String, Integer> otherLevelMap = new HashMap<>();
        otherLevelMap.put(other.getId(), 0);

        Set<VocabularyTerm> myCrtLevel = new HashSet<>();
        myCrtLevel.add(this);
        Set<VocabularyTerm> otherCrtLevel = new HashSet<>();
        otherCrtLevel.add(other);

        for (int l = 1; l <= distance && (!myCrtLevel.isEmpty() || !otherCrtLevel.isEmpty()); ++l) {
            distance = Math.min(distance, processAncestorsAtDistance(l, myCrtLevel, myLevelMap, otherLevelMap));
            distance = Math.min(distance, processAncestorsAtDistance(l, otherCrtLevel, otherLevelMap, myLevelMap));
        }
        return distance == Long.MAX_VALUE ? -1 : distance;
    }

    private long processAncestorsAtDistance(int localDistance, Set<VocabularyTerm> sourceUnprocessedAncestors,
            Map<String, Integer> sourceDistanceMap, Map<String, Integer> targetDistanceMap) {
        long minDistance = Long.MAX_VALUE;
        Set<VocabularyTerm> nextLevel = new HashSet<>();
        for (VocabularyTerm term : sourceUnprocessedAncestors) {
            for (VocabularyTerm parent : term.getParents()) {
                if (sourceDistanceMap.containsKey(parent.getId())) {
                    continue;
                }
                if (targetDistanceMap.containsKey(parent.getId())) {
                    minDistance = Math.min(minDistance, targetDistanceMap.get(parent.getId()) + localDistance);
                }
                nextLevel.add(parent);
                sourceDistanceMap.put(parent.getId(), localDistance);
            }
        }
        sourceUnprocessedAncestors.clear();
        sourceUnprocessedAncestors.addAll(nextLevel);

        return minDistance;
    }

    @Override
    public JSONObject toJSON() {
        JSONObject json = new JSONObject();

        for (Map.Entry<String, ? extends Object> field : getEntrySet()) {
            addAsCorrectType(json, field.getKey(), field.getValue());
        }
        json.put(TRANSLATED_NAME_KEY, getTranslatedName());
        json.put(TRANSLATED_DESCRIPTION_KEY, getTranslatedDescription());
        if (this.parents != null && !this.parents.isEmpty()) {
            JSONArray parentsJson = new JSONArray();
            for (VocabularyTerm parent : this.parents) {
                JSONObject parentJSON = new JSONObject();
                parentJSON.put(ID_KEY, parent.getId());
                parentJSON.put(NAME_KEY, parent.getName());
                parentJSON.put(TRANSLATED_NAME_KEY, parent.getTranslatedName());
                parentsJson.put(parentJSON);
            }
            json.put("parents", parentsJson);
        }

        return json;
    }

    /**
     * Get all the entries in this document.
     *
     * @return the entries as Map.Entry instances, or an empty collection if no entries are set
     */
    protected Set<Map.Entry<String, Object>> getEntrySet() {
        if (isNull()) {
            return Collections.emptySet();
        }
        Set<String> keys = this.doc.keySet();
        Set<Map.Entry<String, Object>> result = new LinkedHashSet<>(keys.size());
        for (String key : keys) {
            result.add(new AbstractMap.SimpleImmutableEntry<>(key, get(key)));
        }
        return result;
    }

    private void addAsCorrectType(JSONObject json, String name, Object toAdd) {
        if (toAdd instanceof Collection) {
            JSONArray array = new JSONArray();
            for (Object item : Collection.class.cast(toAdd)) {
                array.put(item);
            }
            json.put(name, array);
        } else {
            json.put(name, toAdd);
        }
    }

    @Override
    public int hashCode() {
        String id = getId();
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof VocabularyTerm)) {
            return false;
        }
        return StringUtils.equals(getId(), ((VocabularyTerm) obj).getId());
    }

    /**
     * Returns whether the Solr document defining this vocabulary term is {@code null}.
     *
     * @return {@code true} if this vocabulary term doesn't have a valid Solr document set, and is thus unusable
     */
    protected boolean isNull() {
        return this.doc == null;
    }

    /**
     * Gets the values corresponding to the target key.
     *
     * @param key the name of the field to retrieve
     * @return the collection of values, or {@code null} if no values are set for the target key
     */
    protected Collection<Object> getValues(String key) {
        if (isNull()) {
            return null;
        }
        @SuppressWarnings("unchecked")
        Collection<Object> values = this.doc.getFieldValues(key);
        return values;
    }

    /**
     * Retrieves the first value corresponding to the target key. This may be the only value set for a simple field, or
     * the first in a collection of values for a multi-valued field.
     *
     * @param key the name of the field to retrieve
     * @return the first value, or {@code null} if there's no value set for the target key
     */
    protected Object getFirstValue(String key) {
        Collection<Object> values = getValues(key);
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        return IterableUtils.get(values, 0);
    }

    /**
     * The field "term_category" in {@code this.doc} can contain the term itself. It appears that this only happens with
     * HPO. To avoid this problem, and to avoid writing a separate implementation for HPO specifically, this method
     * checks for existence of the term in the term_category and takes it out.
     */
    private void removeSelfFromAncestors() {
        Object value = getFirstValue(ANCESTORS_KEY);
        if (!(value instanceof List)) {
            return;
        }
        @SuppressWarnings("unchecked")
        List<String> listValue = (List<String>) value;
        listValue.remove(this.getId());
    }

    /**
     * Returns the term set for the {@link #getAncestorsAndSelf()} method.
     *
     * @return a set of identifiers, containing at least the identifier of this term
     */
    protected Set<VocabularyTerm> getUncachedAncestorsAndSelf() {
        Collection<Object> termSet = new LinkedHashSet<>();
        termSet.add(this.getId());
        if (getValues(ANCESTORS_KEY) != null) {
            termSet.addAll(getValues(ANCESTORS_KEY));
        }
        return new LazySolrTermSet(termSet, this.vocabulary);
    }

    protected Locale getCurrentLocale() {
        try {
            LocalizationContext lc = ComponentManagerRegistry.getContextComponentManager()
                    .getInstance(LocalizationContext.class);
            Locale result = lc.getCurrentLocale();
            if (result == null) {
                result = Locale.ROOT;
            }
            return result;
        } catch (Exception ex) {
            return Locale.ROOT;
        }
    }
}