Package org.apache.marmotta.kiwi.reasoner.engine

Source Code of org.apache.marmotta.kiwi.reasoner.engine.ReasoningEngine$SKWRLReasoner

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.marmotta.kiwi.reasoner.engine;

import com.google.common.base.Equivalence;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.EmptyIteration;
import info.aduna.iteration.Iterations;
import info.aduna.iteration.SingletonIteration;
import org.apache.marmotta.commons.sesame.model.StatementCommons;
import org.apache.marmotta.kiwi.model.caching.TripleTable;
import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
import org.apache.marmotta.kiwi.model.rdf.KiWiResource;
import org.apache.marmotta.kiwi.model.rdf.KiWiTriple;
import org.apache.marmotta.kiwi.model.rdf.KiWiUriResource;
import org.apache.marmotta.kiwi.reasoner.model.exception.ReasoningException;
import org.apache.marmotta.kiwi.reasoner.model.exception.UnjustifiedTripleException;
import org.apache.marmotta.kiwi.reasoner.model.program.*;
import org.apache.marmotta.kiwi.reasoner.model.query.QueryResult;
import org.apache.marmotta.kiwi.reasoner.persistence.KiWiReasoningConnection;
import org.apache.marmotta.kiwi.reasoner.persistence.KiWiReasoningPersistence;
import org.apache.marmotta.kiwi.sail.KiWiSailConnection;
import org.apache.marmotta.kiwi.transactions.api.TransactionListener;
import org.apache.marmotta.kiwi.transactions.api.TransactionalSail;
import org.apache.marmotta.kiwi.transactions.model.TransactionData;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailException;
import org.openrdf.sail.helpers.SailConnectionWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* This class implements the evaluation of reasoning programs on the triple store. It has two different modes:
* <ul>
* <li>incremental reasoning: when new triples are added or old triples are removed, the inferred information is updated accordingly</li>
* <li>full reasoning: (re-)runs the reasoning process over all triples currently contained in the triple store</li>
* </ul>
* Since reasoning can require some time, the actual execution of incremental reasonong is implemented in a
* producer-consumer style. When new transaction data is available, it is added to a queue. A separate reasoning
* thread then takes new transaction data and processes the rules asynchronously.
* <p/>
* The reasoning engine uses its own connection to the database to carry out reasoning tasks.
* <p/>
* TODO: we need to clarify conceptually whether it would be correct to run several reasoner threads in parallel.
* In theory, reasoning here is strictly monotonic so there should not be a problem. In practice, we might miss
* certain triples because the order of transactions might be messed up. Maybe it makes more sense to parallelize
* the execution or rules instead.
* <p/>
* User: Sebastian Schaffert (sschaffert@apache.org)
*/
public class ReasoningEngine implements TransactionListener {

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

    private static final String TASK_GROUP = "Reasoner";

    /**
     * A queue of transaction data objects of committed transactions, will be consumed by the reasoner
     * thread in incremental reasoning.
     */
    private LinkedBlockingQueue<TransactionData> reasoningQueue;


    /**
     * A direct connection to the database to perform queries and store program-related information
     */
    private KiWiReasoningPersistence persistence;


    /**
     * A connection to the underlying KiWiStore to store new inferred triples and to access the value
     * factory.
     */
    private TransactionalSail        store;

    /**
     * Configuration for this reasoning engine.
     */
    private ReasoningConfiguration   config;

    /**
     * In-memory cache of the currently active reasoning programs, (re-)initialized by {@link #loadPrograms()}.
     */
    private List<Program> programs;

    /**
     * In-memory cache to store all patterns that are candidates for matching triples and accessing the rules
     * they belong to.
     */
    private Multimap<Pattern,Rule> patternRuleMap;

    /**
     * Internal counter to count executions of the reasoner (informational purposes only)
     */
    private static long taskCounter = 0;

    private boolean isshutdown = false;

    /**
     * The worker thread for the reasoner.
     */
    private SKWRLReasoner reasonerThread;

    protected static Equivalence<Statement> equivalence = StatementCommons.quadrupleEquivalence();

    /**
     * A lock to ensure that only once thread at a time is carrying out persistence
     */
    private Lock persistenceLock;

    // for mocking
    protected ReasoningEngine() {

    }

    public ReasoningEngine(KiWiReasoningPersistence persistence, TransactionalSail store, ReasoningConfiguration config) {
        this.persistence = persistence;
        this.store = store;
        this.config = config;
        this.persistenceLock = new ReentrantLock();

        loadPrograms();

        this.reasoningQueue = new LinkedBlockingQueue<TransactionData>();
        this.reasonerThread = new SKWRLReasoner();
    }

    public void loadPrograms() {
        log.info("program configuration changed, reloading ...");
        patternRuleMap = HashMultimap.<Pattern,Rule>create();

        try {
            KiWiReasoningConnection connection = persistence.getConnection();
            try {
                programs       = Iterations.asList(connection.listPrograms());

                for(Program p : programs) {
                    for(Rule rule : p.getRules()) {
                        for(Pattern pattern : rule.getBody()) {
                            patternRuleMap.put(pattern,rule);
                        }
                    }
                }
            } finally {
                connection.close();
            }
        } catch (SQLException ex) {
            programs = Collections.emptyList();
            log.warn("cannot load reasoning programs, reasoning disabled (error message: {})", ex.getMessage());
        }
    }


    public void programChanged(Program program) {
    }

    /**
     * In case a new rule has been added to one of the reasoning programs, process only this rule addition
     * incrementally. Since the reasoner is strictly monotonic, this is sufficient.
     *
     * @param rule
     */
    public void notifyAddRule(Rule rule) {
        startTask("Addition of rule " + rule.getName(), TASK_GROUP);

        log.debug("processing new rule: {}", rule);

        try {
            updateTaskStatus("processing new rule ...");
            processRule(rule, null, null);

        } catch(Exception ex) {
            log.error("error while processing rule",ex);

            return;
        } finally {
            endTask();
        }
    }

    /**
     * In case an existing rule has been removed from one of the reasoning programs, remove all triples
     * that are based on this rule by consulting the justifications that use this rule. Since the reasoner
     * is strictly monotonic, this is sufficient to get the correct set of materialized triples.
     *
     */
    public void notifyRemoveRules() {
        startTask("Removing Rules", TASK_GROUP);

        // clean up justifications depending on the rule
        updateTaskStatus("cleaning up unsupported triples");


        try {
            KiWiReasoningConnection connection = persistence.getConnection();
            try {

                // this is done automatically now when updating or deleting a program:
                // removeJustificationsByQuery("reasoner.listJustificationsByRule", ImmutableMap.of("rule", (Object) rule));

                // then remove all inferred triples that are no longer supported
                cleanupUnsupported(connection);

                // and finally garbage collect those triples that are inferred and deleted
                // garbage collection is now carried out by a thread in the triple store
                //garbageCollectTriples();
            } catch (SQLException ex) {
                connection.rollback();
                throw ex;
            } finally {
                connection.close();
            }
        } catch (SailException ex) {
            log.error("REPOSITORY ERROR: could not clean up unsupported triples, database state will be inconsistent! Message: {}", ex.getMessage());
            log.debug("Exception details:", ex);
        } catch (SQLException ex) {
            log.error("DATABASE ERROR: could not clean up justifications for triples, database state will be inconsistent! Message: {}", ex.getMessage());
            log.debug("Exception details:", ex);
        }


        endTask();
    }



    /**
     * Incrementally apply the updates that are contained in the transaction
     * data to the reasoning programs that are in the system.
     * <p/>
     * Called after a transaction has committed. The transaction data will contain all changes done in the transaction since
     * the last commit. This method should be used in case the transaction listener aims to perform additional activities
     * in a new transaction or outside the transaction management, e.g. notifying a server on the network, adding
     * data to a cache, or similar.
     *
     * @param data
     */
    @Override
    public void afterCommit(TransactionData data) {
        if( (data.getAddedTriples().size() > 0 || data.getRemovedTriples().size() > 0) && patternRuleMap.size() > 0) {

            reasoningQueue.remove(data);
            if (!reasoningQueue.offer(data)) {
                log.info("waiting for reasoning queue to become available ...");
                try {
                    reasoningQueue.put(data);
                    log.info("reasoning queue available, added data");
                } catch (InterruptedException e) {
                    log.error("interrupted while waiting for reasoning queue to become available ...");
                }
            };


        }
    }

    /**
     * Called before a transaction commits. The transaction data will contain all changes done in the transaction since
     * the last commit. This method should be used in case the transaction listener aims to perform additional activities
     * in the same transaction, like inserting or updating database tables.
     *
     * @param data
     */
    @Override
    public void beforeCommit(TransactionData data) {
        // do nothing
    }

    /**
     * Called when a transaction rolls back.
     */
    @Override
    public void rollback(TransactionData data) {
        // do nothing
    }


    /**
     * Start a new reasoner task to collect status messages. Informational purposes only.
     * @param name
     */
    protected void startTask(String name, String taskGroup) {
        // if a task is already running, this should create a nested subtask
    }

    /**
     * Stop the currently active reasoning task. Informational purposes only.
     */
    protected void endTask() {

    }


    /**
     * Update the status of the reasoner task with the given message. This will simply send all update messages to
     * registered status listeners.
     * @param message
     */
    protected void updateTaskStatus(String message) {

    }

    /**
     * Update the task progress (if any)
     * @param progress
     */
    protected void updateTaskProgress(int progress) {

    }

    /**
     * Set the maximum steps needed in the task progress (if any)
     * @param progress
     */
    protected void updateTaskMaxProgress(int progress) {

    }

    private void executeReasoner(TransactionData data) {
        try {
            updateTaskStatus("fetching worklist");
            Set<KiWiTriple> newTriples = StatementCommons.newQuadrupleSet();
            for(Statement stmt : data.getAddedTriples()) {
                KiWiTriple t = (KiWiTriple)stmt;
                if(t.isMarkedForReasoning()) {
                    newTriples.add(t);
                    t.setMarkedForReasoning(false);
                }
            }

            //taskManagerService.setTaskSteps(newTriples.size() + data.getRemovedTriples().size());
            // evaluate the rules for all added triples
            if(newTriples.size() > 0) {
                long start2 = System.currentTimeMillis();
                updateTaskStatus("reasoning over " + newTriples.size() + " new triples");
                processRules(newTriples);
                log.debug("REASONER: reasoning for {} new triples took {} ms overall", newTriples.size(), System.currentTimeMillis() - start2);
            }

            if(data.getRemovedTriples().size() > 0) {
                log.debug("cleaning up justifications and inferences for {} triples",data.getRemovedTriples().size());
                try {
                    KiWiReasoningConnection connection = persistence.getConnection();
                    try {
                        // first clean up justifications that are no longer supported
                        cleanupJustifications(connection, data.getRemovedTriples());


                        // then remove all inferred triples that are no longer supported
                        cleanupUnsupported(connection);

                        // and finally garbage collect those triples that are inferred and deleted
                        // garbage collection is now carried out by a thread in the triple store
                        //garbageCollectTriples();
                        connection.commit();
                    } catch (SQLException ex) {
                        connection.rollback();
                        throw ex;
                    } finally {
                        connection.close();
                    }
                } catch (SailException | SQLException ex) {
                    log.error("REASONING ERROR: could not clean up unsupported triples, database state will be inconsistent! Message: {}", ex.getMessage());
                    log.debug("Exception details:", ex);
                    throw ex;
                }


            }
        } catch (SQLException | SailException | ReasoningException ex) {
            log.error("REASONER: processing of transaction data with ID {} aborted; reason: {}", data.getTransactionId(), ex.getMessage());
        }
    }

    /**
     * Clean all inferred triples and re-run all reasoning rules.
     */
    public void reRunPrograms() {
        final String taskName = "Reasoner Task "+ ++taskCounter + " (full reasoning)";

        startTask("Synchronous " + taskName, TASK_GROUP);
        executeReasoner();
        endTask();
    }


    /**
     * Perform a full reasoning over the triples and rules contained in the database. Will first remove all existing
     * inferred triples and justifications and then evaluate each of the rules in turn.
     */
    private void executeReasoner() {
        // clean up all justifications
        updateTaskStatus("removing old justifications");


        try {
            KiWiReasoningConnection connection = persistence.getConnection();
            try {

                // remove all justifications from the database
                connection.deleteJustifications();

                // clean up inferred triples by removing them from the triple store; the transaction system should take care of the rest
                cleanupUnsupported(connection);

                // and finally garbage collect those triples that are inferred and deleted
                // garbage collection is now carried out by a thread in the triple store
                //garbageCollectTriples();
                connection.commit();
            } catch (SQLException ex) {
                connection.rollback();
                throw ex;
            } finally {
                connection.close();
            }
        } catch (SailException ex) {
            log.error("REPOSITORY ERROR: could not clean up unsupported triples, database state will be inconsistent! Message: {}", ex.getMessage());
            log.debug("Exception details:", ex);
        } catch (SQLException ex) {
            log.error("DATABASE ERROR: could not clean up justifications for triples, database state will be inconsistent! Message: {}", ex.getMessage());
            log.debug("Exception details:", ex);
        }

        // process the rules over the whole dataset
        try {
            updateTaskStatus("processing rules ...");

            //Set<Callable<Boolean>> tasks = new HashSet<Callable<Boolean>>();
            for(Program p : programs) {
                for(final Rule rule : p.getRules()) {
                    startTask("Rule Processing", TASK_GROUP);
                    updateTaskStatus("processing rule " + rule.getName() + " ...");
                    processRule(rule, null, null);
                    endTask();
                    // TODO: multithreading is currently not working reliably (some nodes might get inserted twice)

/*
                    tasks.add(new Callable<Boolean>() {
                        @Override
                        public Boolean call() throws Exception {
                            startTask("Rule Processing", TASK_GROUP);
                            updateTaskStatus("processing rule " + rule.getName() + " ...");
                            processRule(rule, null, null);
                            endTask();
                            return Boolean.TRUE;
                        }
                    });
*/
                }
            }
            //workers.invokeAll(tasks);
        } catch(Exception ex) {
            log.error("error while processing rules", ex);

            return;
        }

    }


    /**
     * This method iterates over all triples that are passed as argument and
     * checks whether they are used as supporting triples justifications. All
     * such justifications are removed. Triples that are no longer supported
     * will later be cleaned up by {@link #cleanupUnsupported(org.apache.marmotta.kiwi.reasoner.persistence.KiWiReasoningConnection)}
     *
     * @param removedTriples
     */
    private void cleanupJustifications(KiWiReasoningConnection connection, TripleTable<Statement> removedTriples) throws SQLException {
        updateTaskStatus("cleaning up justifications for " + removedTriples.size() + " removed triples");
        for(Statement stmt : removedTriples) {
            KiWiTriple t = (KiWiTriple)stmt;
            connection.deleteJustifications(t);
        }
    }


    /**
     * Cleanup inferred triples that are no longer supported by any justification.
     */
    private void cleanupUnsupported(KiWiReasoningConnection connection) throws SQLException, SailException {
        updateTaskStatus("cleaning up unsupported triples");

        int count = 0, total = 0;

        startTask("Unsupported Triple Cleaner", TASK_GROUP);
        updateTaskStatus("loading unsupported triples");

        CloseableIteration<KiWiTriple,SQLException> tripleIterator = connection.listUnsupportedTriples();
        try {
            if(tripleIterator.hasNext()) {

                updateTaskStatus("deleting unsupported triples");
                SailConnection tc = store.getConnection();
                KiWiSailConnection ic = getWrappedConnection(tc);
                try {
                    tc.begin();
                    while(tripleIterator.hasNext()) {
                        ic.removeInferredStatement(tripleIterator.next());
                        count++;
                    }
                    log.debug("removed {} unsupported triples",count);
                    tc.commit();
                } catch(SailException ex) {
                    ic.rollback();
                    throw ex;
                } finally {
                    ic.close();
                }
            }
        } finally {
            Iterations.closeCloseable(tripleIterator);

        }
    }


    /**
     *
     * @param addedTriples
     */
    private void processRules(final Set<KiWiTriple> addedTriples) throws SQLException, SailException, ReasoningException {
        updateTaskStatus("processing rules ...");
        // select the rules that have at least one matching pattern; the match method will
        // return a set of variable bindings that we will be used to prepopulate the bindings
        for(final Pattern pattern : patternRuleMap.keySet()) {
            for(KiWiTriple triple : addedTriples) {
                QueryResult match = matches(pattern,triple);
                if(match != null) {
                    for(Rule rule : patternRuleMap.get(pattern)) {
                        log.debug("REASONER(rule '{}'): pattern {} matched with triple {}", rule.getName(), pattern.toString(), triple.toString());
                        processRule(rule, match, pattern);
                    }
                }
            }
        }
    }

    /**
     * Process the rule given as argument. The set of bindings passed as argument is used as a seed of
     * bindings and will be further populated by pattern matches. The set of justifications is passed over
     * from previous calls so that justifications can be persisted in a batch.
     *
     * @param rule
     * @param match
     */
    private void processRule(Rule rule, QueryResult match, Pattern p) throws SQLException, SailException, ReasoningException {

        // get the variable bindings for the rule evaluation
        log.debug("REASONER(rule '{}'): evaluating rule body {} ...", rule.getName() != null ? rule.getName() : rule.getId(), rule);

        // create a collection consisting of the body minus the pattern that already matched
        Set<Pattern> body = new HashSet<Pattern>(rule.getBody());

        if(p != null) {
            body.remove(p);
        }

        CloseableIteration<QueryResult, SQLException> bodyResult;

        KiWiReasoningConnection connection = persistence.getConnection();
        SailConnection     sail = store.getConnection();
        KiWiSailConnection isail = getWrappedConnection(sail);
        try {

            // if there are further patterns, evaluate them; if the matched pattern was the only pattern, then
            // simply take the match as binding
            if(body.size() > 0) {
                bodyResult = connection.query(body,match,null,null,true);
            } else if(match != null) {
                bodyResult = new SingletonIteration<QueryResult, SQLException>(match);
            } else {
                bodyResult = new EmptyIteration<QueryResult, SQLException>();
            }

            // construct triples out of the bindings and the rule heads
            long counter = 0;

            // initialise a new set of justifications
            Set<Justification> justifications = new HashSet<Justification>();

            sail.begin();
            while(bodyResult.hasNext()) {
                QueryResult row = bodyResult.next();
                Map<VariableField,KiWiNode> binding = row.getBindings();

                Resource subject = null;
                URI property = null;
                Value object;

                if(rule.getHead().getSubject() != null && rule.getHead().getSubject().isVariableField()) {
                    if(!binding.get(rule.getHead().getSubject()).isUriResource() && !binding.get(rule.getHead().getSubject()).isAnonymousResource()) {
                        log.info("cannot use value {} as subject, because it is not a resource",binding.get(rule.getHead().getSubject()));
                        continue;
                    }
                    subject = (KiWiResource)binding.get(rule.getHead().getSubject());
                } else if(rule.getHead().getSubject() != null && rule.getHead().getSubject().isResourceField()) {
                    subject = ((ResourceField)rule.getHead().getSubject()).getResource();
                } else
                    throw new IllegalArgumentException("Subject of rule head may only be a variable or a resource; rule: "+rule);

                if(rule.getHead().getProperty() != null && rule.getHead().getProperty().isVariableField()) {
                    if(!binding.get(rule.getHead().getProperty()).isUriResource()) {
                        log.info("cannot use value {} as property, because it is not a URI resource",binding.get(rule.getHead().getProperty()));
                        continue;
                    }
                    property = (KiWiUriResource)binding.get(rule.getHead().getProperty());
                } else if(rule.getHead().getProperty() != null && rule.getHead().getProperty().isResourceField()) {
                    property = (KiWiUriResource)((ResourceField)rule.getHead().getProperty()).getResource();
                } else
                    throw new IllegalArgumentException("Property of rule head may only be a variable or a resource; rule: "+rule);

                if(rule.getHead().getObject() != null && rule.getHead().getObject().isVariableField()) {
                    object = binding.get(rule.getHead().getObject());
                } else if(rule.getHead().getObject() != null && rule.getHead().getObject().isResourceField()) {
                    object = ((ResourceField)rule.getHead().getObject()).getResource();
                } else if(rule.getHead().getObject() != null && rule.getHead().getObject().isLiteralField()) {
                    object = ((LiteralField)rule.getHead().getObject()).getLiteral();
                } else
                    throw new IllegalArgumentException("Object of rule head may only be a variable, a literal, or a resource; rule: "+rule);


                KiWiTriple triple = isail.addInferredStatement(subject, property, object);

                Justification justification = new Justification();
                justification.setTriple(triple);
                justification.getSupportingRules().add(rule);
                justification.getSupportingTriples().addAll(row.getJustifications());
                justifications.add(justification);

                // when the batch size is reached, commit the transaction, save the justifications, and start a new
                // transaction and new justification set
                if(++counter % config.getBatchSize() == 0) {
                    persistenceLock.lock();

                    try {

                        sail.commit();

                        log.debug("adding {} justifications",justifications.size());

                        updateTaskStatus("storing justifications ...");
                        Set<Justification> baseJustifications = getBaseJustifications(connection,justifications);

                        if(config.isRemoveDuplicateJustifications()) {
                            removeDuplicateJustifications(connection,baseJustifications);
                        }

                        log.debug("{} justifications added after resolving inferred triples", baseJustifications.size());

                        // persist the justifications that have been created in the rule processing
                        if(baseJustifications.size() > 0) {
                            connection.storeJustifications(baseJustifications);
                        }
                        connection.commit();
                        sail.begin();
                    } finally {
                        persistenceLock.unlock();
                    }
                    justifications.clear();
                }
            }

            persistenceLock.lock();
            try {
                sail.commit();

                log.debug("adding {} justifications",justifications.size());
                updateTaskStatus("storing justifications ...");
                Set<Justification> baseJustifications = getBaseJustifications(connection,justifications);

                if(config.isRemoveDuplicateJustifications()) {
                    removeDuplicateJustifications(connection,baseJustifications);
                }

                // persist the justifications that have been created in the rule processing
                if(baseJustifications.size() > 0) {
                    connection.storeJustifications(baseJustifications);
                }

                log.debug("{} justifications added after resolving inferred triples", baseJustifications.size());

                Iterations.closeCloseable(bodyResult);
                connection.commit();
            } finally {
                persistenceLock.unlock();
            }
        } catch(SailException | SQLException | ReasoningException ex) {
            log.error("REASONING ERROR: could not process rule, database state will be inconsistent! Message: {}",ex.getMessage());
            log.debug("Exception details:",ex);

            connection.rollback();
            sail.rollback();
            throw ex;
        } finally {
            connection.close();
            sail.close();
        }

    }

    /**
     * Return the justifications for the triple passed as argument.
     * @param t
     * @return
     */
    protected Collection<Justification> getJustifications(KiWiReasoningConnection connection, KiWiTriple t, Set<Justification> transactionJustifications) throws SQLException {
        HashSet<Justification> justifications = new HashSet<Justification>();
        Iterations.addAll(connection.listJustificationsForTriple(t), justifications);
        for(Justification j : transactionJustifications) {
            if(equivalence.equivalent(j.getTriple(), t)) {
                justifications.add(j);
            }
        }
        return justifications;
    }

    /**
     * For all justifications contained in the set passed as argument, create corresponding base justifications,
     * i.e. justifications that only contain base triples and no inferred triples.
     *
     * @param justifications
     * @return
     */
    protected Set<Justification> getBaseJustifications(KiWiReasoningConnection connection, Set<Justification> justifications) throws SQLException, ReasoningException {
        Set<Justification> baseJustifications = new HashSet<Justification>();
        Map<KiWiTriple,Collection<Justification>> justificationCache = StatementCommons.newQuadrupleMap();

        for(Justification justification : justifications) {
            KiWiTriple triple = justification.getTriple();

            Justification newJustification = new Justification();
            newJustification.setSupportingRules(justification.getSupportingRules());
            newJustification.setTriple(triple);

            Set<Justification> tripleJustifications = Collections.singleton(newJustification);

            // resolve inferred triples by replacing them by their justifications
            for(KiWiTriple support : justification.getSupportingTriples()) {
                if(support.isInferred()) {
                    Collection<Justification> supportJustifications = justificationCache.get(support);
                    // cache justifications of triple in case they are needed again in this run
                    if(supportJustifications == null || supportJustifications.size() == 0) {
                        supportJustifications = getJustifications(connection, support, baseJustifications);
                        justificationCache.put(support,supportJustifications);
                    }

                    if(supportJustifications.size() == 0) {
                        throw new UnjustifiedTripleException("error: inferred triple is not justified!", support);
                    }

                    // mix the two sets
                    Set<Justification> oldTripleJustifications = tripleJustifications;
                    tripleJustifications = new HashSet<Justification>();
                    for(Justification j1 : oldTripleJustifications) {
                        for(Justification j2 : supportJustifications) {
                            Justification j3 = new Justification();
                            j3.setTriple(triple);
                            j3.getSupportingTriples().addAll(j1.getSupportingTriples());
                            j3.getSupportingTriples().addAll(j2.getSupportingTriples());
                            j3.getSupportingRules().addAll(j1.getSupportingRules());
                            j3.getSupportingRules().addAll(j2.getSupportingRules());
                            tripleJustifications.add(j3);
                        }
                    }
                } else {
                    for(Justification j : tripleJustifications) {
                        j.getSupportingTriples().add(support);
                    }
                }
            }

            baseJustifications.addAll(tripleJustifications);
        }
        return baseJustifications;
    }

    /**
     * The reasoner might create identical justifications in the process of generating the base justifications
     * for a triple. This method removes duplicates at the expense of additional computation time.
     * @param justifications
     */
    private void removeDuplicateJustifications(KiWiReasoningConnection connection, Set<Justification> justifications) throws SQLException {
        // remove duplicate justifications
        Map<KiWiTriple,Collection<Justification>> justificationCache = StatementCommons.newQuadrupleMap();
        for(Iterator<Justification> it = justifications.iterator(); it.hasNext(); ) {
            Justification j = it.next();

            Collection<Justification> supportJustifications = justificationCache.get(j.getTriple());
            // cache justifications of triple in case they are needed again in this run
            if(supportJustifications == null) {
                supportJustifications = getJustifications(connection, j.getTriple(), Collections.<Justification>emptySet());
                justificationCache.put(j.getTriple(),supportJustifications);
            }

            if(supportJustifications.contains(j)) {
                it.remove();
            }
        }
    }


    protected static QueryResult matches(Pattern pattern, KiWiTriple triple) {
        boolean result = true;

        QueryResult match = new QueryResult();

        if(pattern.getSubject().isResourceField()) {
            result = ((ResourceField)pattern.getSubject()).getResource().equals(triple.getSubject());
        } else if(pattern.getSubject().isVariableField()) {
            KiWiNode binding = match.getBindings().get(pattern.getSubject());
            if(binding != null) {
                result = binding.equals(triple.getSubject());
            } else {
                match.getBindings().put((VariableField) pattern.getSubject(), triple.getSubject());
            }
        }

        if(result && pattern.getProperty().isResourceField()) {
            result = ((ResourceField)pattern.getProperty()).getResource().equals(triple.getPredicate());
        } else if(result && pattern.getProperty().isVariableField()) {
            KiWiNode binding = match.getBindings().get(pattern.getProperty());
            if(binding != null) {
                result = binding.equals(triple.getPredicate());
            } else {
                match.getBindings().put((VariableField) pattern.getProperty(), triple.getPredicate());
            }
        }

        if(result && pattern.getContext() != null && pattern.getContext().isResourceField()) {
            result = ((ResourceField)pattern.getContext()).getResource().equals(triple.getContext());
        } else if(result && pattern.getContext() != null && pattern.getContext().isVariableField()) {
            KiWiNode binding = match.getBindings().get(pattern.getContext());
            if(binding != null) {
                result = binding.equals(triple.getContext());
            } else {
                match.getBindings().put((VariableField) pattern.getContext(), triple.getContext());
            }
        }

        if(result && pattern.getObject().isResourceField()) {
            result = ((ResourceField)pattern.getObject()).getResource().equals(triple.getObject());
        } else if(result && pattern.getObject().isLiteralField()) {
            result = ((LiteralField)pattern.getObject()).getLiteral().equals(triple.getObject());
        } else if(result && pattern.getObject().isVariableField()) {
            KiWiNode binding = match.getBindings().get(pattern.getObject());
            if(binding != null) {
                result = binding.equals(triple.getObject());
            } else {
                match.getBindings().put((VariableField) pattern.getObject(), triple.getObject());
            }
        }


        if(result) {
            match.getJustifications().add(triple);
            return match;
        } else
            return null;
    }

    /**
     * Return true in case the reasoner is currently executing, false otherwise.
     * @return
     */
    public boolean isRunning() {
        return reasonerThread.isRunning() || !reasoningQueue.isEmpty();
    }

    public void shutdown() {
        shutdown(false);
    }

    public void shutdown(boolean force) {
        if(isshutdown)
            return;

        if(force) {
            log.warn("forced shutdown of reasoning service initiated, state will be inconsistent ...");

            reasoningQueue.clear();
            reasonerThread.shutdown(true);

            for(int i = 0; i<5 && isRunning(); i++) {
                log.warn("reasoner not yet finished, waiting for 100 ms (try={})", i+1);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }

            // yes, I know it is unsafe; it is only used when forcefully shutting down on test ends before the database is deleted...
            reasonerThread.stop();

        } else {
            log.info("graceful shutdown of reasoning service initiated ...");

            for(int i = 0; i<20 && isRunning(); i++) {
                log.warn("reasoner not yet finished, waiting for 1 seconds (try={})", i+1);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
            }

            reasonerThread.shutdown(false);
        }

        isshutdown = true;
    }

    /**
     * Return the KiWiSailConnection underlying a given sail connection. The method will follow wrapped
     * connections until it finds the KiWiSailConnection, or otherwise throws a SailException.
     * @param connection
     * @return
     */
    private KiWiSailConnection getWrappedConnection(SailConnection connection) throws SailException {
        SailConnection it = connection;
        while(it instanceof SailConnectionWrapper) {
            it = ((SailConnectionWrapper) it).getWrappedConnection();
            if(it instanceof KiWiSailConnection) {
                return (KiWiSailConnection) it;
            }
        }
        throw new SailException("no underlying KiWiSailConnection found for connection");
    }

    private static int indexerCounter = 0;

    private class SKWRLReasoner extends Thread {
        private boolean shutdown = false;
        private boolean running  = false;
        private boolean forceshutdown = false;

        private SKWRLReasoner() {
            super("SKWRL Reasoner " + ++indexerCounter);
            setDaemon(true);
            start();
        }

        public void shutdown(boolean force) {
            log.info("REASONER: signalling shutdown to reasoner thread");
            shutdown = true;
            forceshutdown = force;
            this.interrupt();
        }

        public boolean isRunning() {
            return running;
        }

        @Override
        public void run() {
            log.info("{} starting up ...", getName());

            startTask(getName(), TASK_GROUP);

            while (!forceshutdown && (!shutdown || reasoningQueue.size() > 0)) {
                running = false;
                try {
                    updateTaskStatus("idle");

                    TransactionData data = reasoningQueue.take();
                    running = true;

                    updateTaskMaxProgress(reasoningQueue.size());

                    executeReasoner(data);
                } catch (InterruptedException ex) {

                } catch (RuntimeException ex) {
                    // can happen on forced shutdown
                } catch (Exception ex) {
                    log.warn("reasoning task threw an exception",ex);
                }
            }
            running = false;
            try {
                endTask();
            } catch (Exception ex) {
            }
            log.info("{} shutting down ...", getName());
        }
    }

}
TOP

Related Classes of org.apache.marmotta.kiwi.reasoner.engine.ReasoningEngine$SKWRLReasoner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.