All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cornell.mannlib.vitro.webapp.searchindex.extensions.LabelsAcrossContextNodes Maven / Gradle / Ivy

The newest version!
/* $This file is distributed under the terms of the license in LICENSE$ */

package edu.cornell.mannlib.vitro.webapp.searchindex.extensions;

import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXT;
import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXTUNSTEMMED;
import static edu.cornell.mannlib.vitro.webapp.utils.sparqlrunner.SparqlQueryRunner.createSelectQueryContext;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.vocabulary.RDFS;

import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.VClass;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputDocument;
import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
import edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.DocumentModifier;
import edu.cornell.mannlib.vitro.webapp.searchindex.indexing.IndexingUriFinder;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ContextModelsUser;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;

/**
 * If an individual has context nodes then the search document for that
 * individual should include the labels of the partners across those nodes. The
 * labels will be added to the ALLTEXT and ALLTEXTUNSTEMMED fields.
 *
 * We must specify what property leads to a context node (incoming), and what
 * property leads from a context node (outgoing). We may add restrictions to say
 * that this only applies to individuals of certain types. We may also restrict
 * the type of the applicable context nodes.
 *
 * An instance of this class acts as both a DocumentModifier and an
 * IndexingUriFinder:
 *
 * As a DocumentModifier, it looks across approved context nodes to fetch the
 * labels of the partners.
 *
 * As an IndexingUriFinder, it recognizes that this relationship can be changed
 * by a change to a "label" statement, or to a "relates" property, and finds all
 * partners as candidates for reindexing.
 *
 * 
 * Configuration:
 *     rdfs:label -- Optional. Appears in the timings and debug statements.
 *     :hasIncomingProperty -- Required. Property leading to the context node.
 *     :hasOutgoingProperty -- Required. Property leading from the context node.
 *     :hasTypeRestriction -- Optional. Match any. If none, then no restriction.
 *     :appliesToContextNodeType -- Optional. Match any. If none, then no restriction.
 * 
*/ public class LabelsAcrossContextNodes implements IndexingUriFinder, DocumentModifier, ContextModelsUser { private static final Log log = LogFactory .getLog(LabelsAcrossContextNodes.class); private RDFService rdfService; /** * A name to be used in logging, to identify this instance. If not provided, * then a descriptive label will be created. */ private String label; /** * The URI of the property that leads into the context node. Required. */ private String incomingPropertyUri; /** * The URI of the property that leads from the context node. Required. */ private String outgoingPropertyUri; /** * URIs of the types of individuals to whom this instance applies. * * If this is not empty and an individual does not have any of these types, * then skip that individual. */ private Set typeRestrictions = new HashSet<>(); /** * URIs of the types of acceptable context nodes. * * If this is not empty and a context node does not have any of these types, * then skip that context node's label. */ private Set contextNodeClasses = new HashSet<>(); @Override public void setContextModels(ContextModelAccess models) { this.rdfService = models.getRDFService(); } @Property(uri = "http://www.w3.org/2000/01/rdf-schema#label", maxOccurs = 1) public void setLabel(String l) { label = l; } @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasIncomingProperty", minOccurs = 1, maxOccurs = 1) public void setIncomingProperty(String incomingUri) { incomingPropertyUri = incomingUri; } @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasOutgoingProperty", minOccurs = 1, maxOccurs = 1) public void setOutgoingProperty(String outgoingUri) { outgoingPropertyUri = outgoingUri; } @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasTypeRestriction") public void addTypeRestriction(String typeUri) { typeRestrictions.add(typeUri); } @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#appliesToContextNodeType") public void addContextNodeClass(String cnc) { contextNodeClasses.add(cnc); } @Validation public void validate() { if (label == null) { label = String.format("%s[types=%s, contextNodeTypes=%s]", this .getClass().getSimpleName(), formatRestrictions(typeRestrictions), formatRestrictions(contextNodeClasses)); } } private String formatRestrictions(Set uris) { if (uris.isEmpty()) { return "ALL"; } else { return localNames(uris).toString(); } } private Set localNames(Set uris) { Set names = new HashSet<>(); for (String uri : uris) { try { names.add(ResourceFactory.createResource(uri).getLocalName()); } catch (Exception e) { log.warn("Failed to parse URI: " + uri, e); names.add(uri); } } return names; } @Override public String toString() { return (label == null) ? super.toString() : label; } // ---------------------------------------------------------------------- // DocumentModifier // ---------------------------------------------------------------------- private static final String LABELS_WITHOUT_RESTRICTION = "" + "PREFIX rdfs: \n" + "SELECT ?label \n" // + "WHERE { \n" // + " ?uri ?incoming ?contextNode . \n" // + " ?contextNode ?outgoing ?partner . \n" // + " ?partner rdfs:label ?label . \n" // + " FILTER( ?uri != ?partner ) \n" // + "} \n"; private static final String LABELS_FOR_SPECIFIC_CONTEXT_NODE_TYPE = "" + "PREFIX rdfs: \n" + "SELECT ?label \n" // + "WHERE { \n" // + " ?uri ?incoming ?contextNode . \n" // + " ?contextNode a ?nodeType . \n" // + " ?contextNode ?outgoing ?partner . \n" // + " ?partner rdfs:label ?label . \n" // + " FILTER( ?uri != ?partner ) \n" // + "} \n"; /** * If this individual is acceptable, locate the labels of any context * partners across acceptable context nodes. Add those labels to the text * fields of the search document. */ @Override public void modifyDocument(Individual ind, SearchInputDocument doc) { if (passesTypeRestriction(ind)) { if (contextNodeClasses.isEmpty()) { addLabelsFromAllContextNodeClasses(ind, doc); } else { for (String contextNodeClass : contextNodeClasses) { addLabelsFromContextNodeClass(ind, doc, contextNodeClass); } } } } private boolean passesTypeRestriction(Individual ind) { if (typeRestrictions.isEmpty()) { return true; } else { for (VClass type : ind.getVClasses()) { if (typeRestrictions.contains(type.getURI())) { return true; } } } return false; } private void addLabelsFromAllContextNodeClasses(Individual ind, SearchInputDocument doc) { addValuesToTextFields( doc, createSelectQueryContext(rdfService, LABELS_WITHOUT_RESTRICTION) .bindVariableToUri("uri", ind.getURI()) .bindVariableToUri("incoming", incomingPropertyUri) .bindVariableToUri("outgoing", outgoingPropertyUri) .execute().toStringFields("label").flatten()); } private void addLabelsFromContextNodeClass(Individual ind, SearchInputDocument doc, String contextNodeClass) { addValuesToTextFields( doc, createSelectQueryContext(rdfService, LABELS_FOR_SPECIFIC_CONTEXT_NODE_TYPE) .bindVariableToUri("uri", ind.getURI()) .bindVariableToUri("nodeType", contextNodeClass) .bindVariableToUri("incoming", incomingPropertyUri) .bindVariableToUri("outgoing", outgoingPropertyUri) .execute().toStringFields("label").flatten()); } private void addValuesToTextFields(SearchInputDocument doc, List values) { for (String value : values) { doc.addField(ALLTEXT, value); doc.addField(ALLTEXTUNSTEMMED, value); } } @Override public void shutdown() { // Nothing to shut down. } // ---------------------------------------------------------------------- // IndexingUriFinder // ---------------------------------------------------------------------- private static final String LOCATE_PARTNERS_WITHOUT_RESTRICTION = "" + "SELECT ?partner \n" // + "WHERE { \n" // + " ?partner ?incoming ?contextNode . \n" // + " ?contextNode ?outgoing ?uri . \n" // + " FILTER( ?uri != ?partner ) \n" // + "} \n"; private static final String LOCATE_PARTNERS_ON_CONTEXT_NODE_TYPE = "" + "SELECT ?partner \n" // + "WHERE { \n" // + " ?partner ?incoming ?contextNode . \n" // + " ?contextNode ?outgoing ?uri . \n" // + " ?contextNode a ?nodeType . \n" // + " FILTER( ?uri != ?partner ) \n" // + "} \n"; private static final String LOCATE_OTHER_PARTNERS_ON_THIS_NODE = "" + "SELECT ?partner \n" // + "WHERE { \n" // + " ?contextNode ?outgoing ?partner . \n" // + " FILTER( ?uri != ?partner ) \n" // + "} \n"; private static final String GET_TYPES = "" // + "SELECT ?type \n" // + "WHERE { \n" // + " ?uri a ?type . \n" // + "} \n"; @Override public void startIndexing() { // Nothing to do. } /** * If this is a "label" statement, check to see if the subject has any * acceptable partners across acceptable context nodes. * * If this is a statement that involves the specified incoming property on * an acceptable context node, check to see if there are any acceptable * partners on this node. */ @Override public List findAdditionalURIsToIndex(Statement stmt) { if (isLabelStatement(stmt)) { return filterByType(locatePartners(stmt)); } else if (isIncomingStatementOnAcceptableContextNode(stmt)) { return filterByType(locateOtherPartners(stmt)); } return Collections.emptyList(); } private boolean isLabelStatement(Statement stmt) { return RDFS.label.getURI().equals(stmt.getPredicate().getURI()); } private Set locatePartners(Statement stmt) { String uri = stmt.getSubject().getURI(); if (contextNodeClasses.isEmpty()) { return locatePartnersWithoutRestriction(uri); } else { Set uris = new HashSet<>(); for (String contextNodeClass : contextNodeClasses) { uris.addAll(locatePartnersAcrossContextNodeClass(uri, contextNodeClass)); } return uris; } } private Set locatePartnersWithoutRestriction(String uri) { return createSelectQueryContext(rdfService, LOCATE_PARTNERS_WITHOUT_RESTRICTION) .bindVariableToUri("uri", uri) .bindVariableToUri("incoming", incomingPropertyUri) .bindVariableToUri("outgoing", outgoingPropertyUri).execute() .toStringFields("partner").flattenToSet(); } private Collection locatePartnersAcrossContextNodeClass( String uri, String contextNodeClass) { return createSelectQueryContext(rdfService, LOCATE_PARTNERS_ON_CONTEXT_NODE_TYPE) .bindVariableToUri("uri", uri) .bindVariableToUri("nodeType", contextNodeClass) .bindVariableToUri("incoming", incomingPropertyUri) .bindVariableToUri("outgoing", outgoingPropertyUri).execute() .toStringFields("partner").flattenToSet(); } private boolean isIncomingStatementOnAcceptableContextNode(Statement stmt) { String subjectUri = stmt.getSubject().getURI(); String predicateUri = stmt.getPredicate().getURI(); return incomingPropertyUri.equals(predicateUri) && (contextNodeClasses.isEmpty() || isAnyMatch( contextNodeClasses, getTypes(subjectUri))); } private boolean isAnyMatch(Set set1, Set set2) { Set matches = new HashSet<>(set1); matches.retainAll(set2); return !matches.isEmpty(); } private Set getTypes(String uri) { return createSelectQueryContext(rdfService, GET_TYPES) .bindVariableToUri("uri", uri).execute().toStringFields("type") .flattenToSet(); } private Set locateOtherPartners(Statement stmt) { if (!stmt.getSubject().isURIResource()) { return Collections.emptySet(); } String nodeUri = stmt.getSubject().getURI(); String objectUri = (stmt.getObject().isURIResource()) ? stmt .getObject().asResource().getURI() : "NO_MATCH"; return createSelectQueryContext(rdfService, LOCATE_OTHER_PARTNERS_ON_THIS_NODE) .bindVariableToUri("contextNode", nodeUri) .bindVariableToUri("uri", objectUri) .bindVariableToUri("outgoing", outgoingPropertyUri).execute() .toStringFields("partner").flattenToSet(); } private List filterByType(Collection uris) { if (typeRestrictions.isEmpty()) { return new ArrayList<>(uris); } else { List filtered = new ArrayList<>(); for (String uri : uris) { if (isAnyMatch(typeRestrictions, getTypes(uri))) { filtered.add(uri); } } return filtered; } } @Override public void endIndexing() { // Nothing to do. } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy