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

org.openrdf.sail.inferencer.fc.CustomGraphQueryInferencer Maven / Gradle / Ivy

There is a newer version: 4.1.2
Show newest version
/* 
 * Licensed to Aduna under one or more contributor license agreements.  
 * See the NOTICE.txt file distributed with this work for additional 
 * information regarding copyright ownership. 
 *
 * Aduna licenses this file to you under the terms of the Aduna BSD 
 * License (the "License"); you may not use this file except in compliance 
 * with the License. See the LICENSE.txt file distributed with this work 
 * for the full License.
 *
 * 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 org.openrdf.sail.inferencer.fc;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import info.aduna.iteration.CloseableIteration;

import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.UnsupportedQueryLanguageException;
import org.openrdf.query.algebra.StatementPattern;
import org.openrdf.query.algebra.Var;
import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
import org.openrdf.query.impl.EmptyBindingSet;
import org.openrdf.query.parser.ParsedGraphQuery;
import org.openrdf.query.parser.QueryParserUtil;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.sail.NotifyingSail;
import org.openrdf.sail.SailConnectionListener;
import org.openrdf.sail.SailException;
import org.openrdf.sail.helpers.NotifyingSailWrapper;
import org.openrdf.sail.inferencer.InferencerConnection;
import org.openrdf.sail.inferencer.InferencerConnectionWrapper;
import org.openrdf.sail.inferencer.fc.config.CustomGraphQueryInferencerConfig;

/**
 * A forward-chaining inferencer that infers new statements using a SPARQL or
 * SeRQL graph query.
 * 
 * @author Dale Visser
 */
public class CustomGraphQueryInferencer extends NotifyingSailWrapper {

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	private ParsedGraphQuery customQuery;

	private ParsedGraphQuery customMatcher;

	private final Collection watchPredicates = new HashSet();

	private final Collection watchSubjects = new HashSet();

	private final Collection watchObjects = new HashSet();

	private boolean hasWatchValues;

	public CustomGraphQueryInferencer() {
		super();
	}

	/**
	 * Create a new custom inferencer.
	 * 
	 * @param language
	 *        language that queryText and matcherText are
	 *        expressed in
	 * @param queryText
	 *        a query that returns an RDF graph of inferred statements to be
	 *        added to the underlying Sail
	 * @param matcherText
	 *        a query that returns an RDF graph of existing inferred statements
	 *        already added previously
	 * @throws MalformedQueryException
	 *         if there is a problem parsing either of the given queries
	 * @throws UnsupportedQueryLanguageException
	 *         if an unsupported query language is specified
	 * @throws SailException
	 *         if a problem occurs interpreting the rule pattern
	 */
	public CustomGraphQueryInferencer(QueryLanguage language, String queryText, String matcherText)
		throws MalformedQueryException, UnsupportedQueryLanguageException, SailException
	{
		super();
		setFields(language, queryText, matcherText);
	}

	/**
	 * Create a new custom inferencer.
	 * 
	 * @param baseSail
	 *        an underlying Sail, such as another inferencer or a SailRepository
	 * @param language
	 *        language that queryText and matcherText are
	 *        expressed in
	 * @param queryText
	 *        a query that returns an RDF graph of inferred statements to be
	 *        added to the underlying Sail
	 * @param matcherText
	 *        a query that returns an RDF graph of existing inferred statements
	 *        already added previously
	 * @throws MalformedQueryException
	 *         if there is a problem parsing either of the given queries
	 * @throws UnsupportedQueryLanguageException
	 * @throws SailException
	 *         if a problem occurs interpreting the rule pattern
	 */
	public CustomGraphQueryInferencer(NotifyingSail baseSail, QueryLanguage language, String queryText,
			String matcherText)
		throws MalformedQueryException, UnsupportedQueryLanguageException, SailException
	{
		super(baseSail);
		setFields(language, queryText, matcherText);
	}

	/**
	 * Called in order to set all the fields needed for the inferencer to
	 * function.
	 * 
	 * @param language
	 *        language that queryText and matcherText are
	 *        expressed in
	 * @param queryText
	 *        a query that returns an RDF graph of inferred statements to be
	 *        added to the underlying Sail
	 * @param matcherText
	 *        a query that returns an RDF graph of existing inferred statements
	 *        already added previously
	 * @throws MalformedQueryException
	 *         if there is a problem parsing either of the given queries
	 * @throws SailException
	 *         if a problem occurs interpreting the rule pattern
	 */
	public final void setFields(QueryLanguage language, String queryText, String matcherText)
		throws MalformedQueryException, SailException
	{
		customQuery = QueryParserUtil.parseGraphQuery(language, queryText, null);
		String matcherQuery = matcherText;
		if (matcherText.trim().isEmpty()) {
			matcherQuery = CustomGraphQueryInferencerConfig.buildMatcherQueryFromRuleQuery(language, queryText);
		}
		customMatcher = QueryParserUtil.parseGraphQuery(language, matcherQuery, null);
		customQuery.getTupleExpr().visit(new QueryModelVisitorBase() {

			@Override
			public void meet(StatementPattern statement)
				throws SailException
			{
				Var var = statement.getSubjectVar();
				if (var.hasValue()) {
					watchSubjects.add(var.getValue());
				}
				var = statement.getPredicateVar();
				if (var.hasValue()) {
					watchPredicates.add(var.getValue());
				}
				var = statement.getObjectVar();
				if (var.hasValue()) {
					watchObjects.add(var.getValue());
				}
			}
		});
		hasWatchValues = !(watchSubjects.isEmpty() && watchPredicates.isEmpty() && watchObjects.isEmpty());
	}

	@Override
	public InferencerConnection getConnection()
		throws SailException
	{
		try {
			InferencerConnection con = (InferencerConnection)super.getConnection();
			return new Connection(con);
		}
		catch (ClassCastException e) {
			throw new SailException(e.getMessage(), e);
		}
	}

	@Override
	public void initialize()
		throws SailException
	{
		super.initialize();
		InferencerConnection con = getConnection();
		try {
			con.begin();
			con.flushUpdates();
			con.commit();
		}
		finally {
			con.close();
		}
	}

	/**
	 * Exposed for test purposes.
	 * 
	 * @return a computed collection of the statement subjects that, when added
	 *         or removed, trigger an update of inferred statements
	 */
	public Collection getWatchSubjects() {
		return Collections.unmodifiableCollection(watchSubjects);
	}

	/**
	 * Exposed for test purposes.
	 * 
	 * @return a computed collection of the statement predicates that, when added
	 *         or removed, trigger an update of inferred statements
	 */
	public Collection getWatchPredicates() {
		return Collections.unmodifiableCollection(watchPredicates);
	}

	/**
	 * Exposed for test purposes.
	 * 
	 * @return a computed collection of the statement objects that, when added or
	 *         removed, trigger an update of inferred statements
	 */
	public Collection getWatchObjects() {
		return Collections.unmodifiableCollection(watchObjects);
	}

	private class Connection extends InferencerConnectionWrapper implements SailConnectionListener {

		/**
		 * Flag indicating whether an update of the inferred statements is needed.
		 */
		private boolean updateNeeded = false;

		private Connection(InferencerConnection con) {
			super(con);
			con.addConnectionListener(this);
		}

		@Override
		public void statementAdded(Statement statement) {
			setUpdateNeededIfMatching(statement);
		}

		@Override
		public void statementRemoved(Statement statement) {
			setUpdateNeededIfMatching(statement);
		}

		private void setUpdateNeededIfMatching(Statement statement) {
			updateNeeded = hasWatchValues ? watchPredicates.contains(statement.getPredicate())
					|| watchSubjects.contains(statement.getSubject())
					|| watchObjects.contains(statement.getObject()) : true;
		}

		@Override
		public void rollback()
			throws SailException
		{
			super.rollback();
			updateNeeded = false;
		}

		@Override
		public void flushUpdates()
			throws SailException
		{
			super.flushUpdates();
			Collection forRemoval = new HashSet(256);
			Collection forAddition = new HashSet(256);
			Resource[] contexts = new Resource[] { null };
			while (updateNeeded) {
				try {
					// Determine which statements should be added and which should be
					// removed
					forRemoval.clear();
					forAddition.clear();
					buildDeltaSets(forRemoval, forAddition);
					for (Statement st : forRemoval) {
						removeInferredStatement(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
					}
					for (Statement st : forAddition) {
						addInferredStatement(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
					}
					updateNeeded = false;
				}
				catch (RDFHandlerException e) {
					Throwable cause = e.getCause();
					if (cause instanceof SailException) {
						throw (SailException)cause;
					}
					else {
						throw new SailException(cause);
					}
				}
				catch (QueryEvaluationException e) {
					throw new SailException(e);
				}
				super.flushUpdates();
			}
		}

		private void buildDeltaSets(Collection forRemoval, Collection forAddition)
			throws SailException, RDFHandlerException, QueryEvaluationException
		{
			evaluateIntoStatements(customMatcher, forRemoval);
			evaluateIntoStatements(customQuery, forAddition);
			logger.debug("existing virtual properties: {}", forRemoval.size());
			logger.debug("new virtual properties: {}", forAddition.size());
			Collection inCommon = new HashSet(forRemoval);
			inCommon.retainAll(forAddition);
			forRemoval.removeAll(inCommon);
			forAddition.removeAll(inCommon);
			logger.debug("virtual properties to remove: {}", forRemoval.size());
			logger.debug("virtual properties to add: {}", forAddition.size());
		}

		private void evaluateIntoStatements(ParsedGraphQuery query, Collection statements)
			throws SailException, RDFHandlerException, QueryEvaluationException
		{
			CloseableIteration bindingsIter = getWrappedConnection().evaluate(
					query.getTupleExpr(), null, EmptyBindingSet.getInstance(), true);
			try {
				ValueFactory factory = getValueFactory();
				while (bindingsIter.hasNext()) {
					BindingSet bindings = bindingsIter.next();
					Value subj = bindings.getValue("subject");
					Value pred = bindings.getValue("predicate");
					Value obj = bindings.getValue("object");
					if (subj instanceof Resource && pred instanceof URI && obj != null) {
						statements.add(factory.createStatement((Resource)subj, (URI)pred, obj));
					}
				}
			}
			finally {
				bindingsIter.close();
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy