Package org.apache.marmotta.kiwi.reasoner.persistence

Source Code of org.apache.marmotta.kiwi.reasoner.persistence.KiWiReasoningConnection

/**
* 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.persistence;

import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.EmptyIteration;
import info.aduna.iteration.Iteration;
import info.aduna.iteration.Iterations;
import info.aduna.iteration.IteratorIteration;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.apache.marmotta.kiwi.caching.KiWiCacheManager;
import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
import org.apache.marmotta.kiwi.model.rdf.KiWiTriple;
import org.apache.marmotta.kiwi.persistence.KiWiConnection;
import org.apache.marmotta.kiwi.persistence.KiWiDialect;
import org.apache.marmotta.kiwi.persistence.KiWiPersistence;
import org.apache.marmotta.kiwi.persistence.util.ResultSetIteration;
import org.apache.marmotta.kiwi.persistence.util.ResultTransformerFunction;
import org.apache.marmotta.kiwi.reasoner.model.program.Field;
import org.apache.marmotta.kiwi.reasoner.model.program.Filter;
import org.apache.marmotta.kiwi.reasoner.model.program.Justification;
import org.apache.marmotta.kiwi.reasoner.model.program.LiteralField;
import org.apache.marmotta.kiwi.reasoner.model.program.Pattern;
import org.apache.marmotta.kiwi.reasoner.model.program.Program;
import org.apache.marmotta.kiwi.reasoner.model.program.ResourceField;
import org.apache.marmotta.kiwi.reasoner.model.program.Rule;
import org.apache.marmotta.kiwi.reasoner.model.program.VariableField;
import org.apache.marmotta.kiwi.reasoner.model.query.QueryResult;
import org.apache.marmotta.kiwi.reasoner.parser.KWRLProgramParser;
import org.apache.marmotta.kiwi.reasoner.parser.ParseException;
import org.openrdf.model.ValueFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* Extends the basic KiWiConnection by functionalities for storing, deleting and querying reasoner programs and rules
* as well as justification information.
* <p/>
* Author: Sebastian Schaffert
*/
public class KiWiReasoningConnection extends KiWiConnection {

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

    private ValueFactory valueFactory;

    private Cache ruleIdCache;

    public KiWiReasoningConnection(KiWiPersistence persistence, KiWiDialect dialect, KiWiCacheManager cacheManager, ValueFactory valueFactory) throws SQLException {
        super(persistence, dialect, cacheManager);

        this.valueFactory = valueFactory;
        this.ruleIdCache = getCacheManager().getCacheByName("reasoning-rule-id");

    }


    /**
     * Store the rule given as argument in the database. This method will always create a new entry in the database
     * and does not check if a rule with the same name or content already exists.
     * @param rule
     * @throws SQLException
     */
    public void storeRule(Rule rule, Map<String, String> namespaces) throws SQLException {
        if(rule.getId() != null) {
            log.warn("rule {} already had a database ID, not persisting", rule);
            return;
        }

        requireJDBCConnection();

        // first create a new entry in the rules table
        rule.setId(getNextSequence("seq.rules"));

        PreparedStatement insertRule = getPreparedStatement("rules.insert");
        synchronized (insertRule) {
            insertRule.setLong(1, rule.getId());
            insertRule.setString(2, rule.getName());
            insertRule.setString(3, rule.getDescription());
            insertRule.setString(4, rule.toString(namespaces));
            insertRule.executeUpdate();
        }

        ruleIdCache.put(new Element(rule.getId(), rule));
    }

    /**
     * Load the rule with the given database ID from the database.
     *
     * @param ruleId
     * @return
     * @throws SQLException
     */
    public Rule loadRuleById(long ruleId, Map<String, String> namespaces) throws SQLException {

        Element cached = ruleIdCache.get(ruleId);

        if(cached != null) {
            return (Rule) cached.getObjectValue();
        } else {
            requireJDBCConnection();

            // load namespaces if they are not yet given
            if(namespaces == null) {
                namespaces = new HashMap<String, String>();
                PreparedStatement loadNamespaces = getPreparedStatement("namespaces.load_by_rule");
                synchronized (loadNamespaces) {
                    loadNamespaces.setLong(1,ruleId);
                    ResultSet namespaceResult = loadNamespaces.executeQuery();
                    while(namespaceResult.next()) {
                        namespaces.put(namespaceResult.getString("ns_prefix"), namespaceResult.getString("ns_uri"));
                    }
                    namespaceResult.close();
                }
            }


            PreparedStatement loadRule = getPreparedStatement("rules.load_by_id");
            synchronized (loadRule) {
                loadRule.setLong(1,ruleId);
                ResultSet result = loadRule.executeQuery();
                try {
                    if(result.next()) {
                        return constructRuleFromDatabase(result,namespaces);
                    } else {
                        return null;
                    }
                } catch (ParseException e) {
                    throw new SQLException("error while parsing rule body",e);
                } finally {
                    result.close();
                }
            }
        }
    }

    /**
     * Load the rule with the given database ID from the database.
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Rule,SQLException> loadRulesByProgram(long programId, final Map<String, String> namespaces) throws SQLException, ParseException {

        requireJDBCConnection();

        PreparedStatement loadRule = getPreparedStatement("rules.load_by_program");
        synchronized (loadRule) {
            loadRule.setLong(1,programId);
            ResultSet result = loadRule.executeQuery();

            return new ResultSetIteration<Rule>(result, new ResultTransformerFunction<Rule>() {
                @Override
                public Rule apply(ResultSet row) throws SQLException {
                    try {
                        return constructRuleFromDatabase(row,namespaces);
                    } catch (ParseException e) {
                        throw new SQLException("error while parsing rule body",e);
                    }
                }
            });
        }
    }


    protected Rule constructRuleFromDatabase(ResultSet row, Map<String, String> namespaces) throws SQLException, ParseException {
        Element cached = ruleIdCache.get(row.getLong("id"));

        if(cached != null) {
            return (Rule) cached.getObjectValue();
        } else {

            Rule result = KWRLProgramParser.parseRule(row.getString("body"),namespaces, valueFactory);
            result.setId(row.getLong("id"));
            result.setName(row.getString("name"));
            result.setDescription(row.getString("description"));

            ruleIdCache.put(new Element(result.getId(), result));

            return result;
        }
    }



    /**
     * Remove the rule given as argument from the database. This will also trigger removing all justifications
     * depending on this rule. The reasoner afterwards needs to clean up in a separate run all inferred triples t
     * hat are no longer justified.
     * @param rule
     * @throws SQLException
     */
    public void removeRule(Rule rule) throws SQLException {
        if(rule.getId() == null) {
            log.warn("rule {} does not have a database ID, cannot delete", rule);
            return;
        }

        requireJDBCConnection();

        PreparedStatement deleteRule = getPreparedStatement("rules.delete_by_id");
        synchronized (deleteRule) {
            deleteRule.setLong(1, rule.getId());
            deleteRule.executeUpdate();
        }

        ruleIdCache.remove(rule.getId());
    }



    /**
     * Store the program given as argument in the database. In case the program does not exist yet, it is
     * created together with all rules and namespace definitions. In case the program already exists, an exception
     * is thrown.
     *
     * @param program
     * @throws SQLException
     */
    public void storeProgram(Program program) throws SQLException {
        if(program.getId() != null) {
            throw new SQLException("Program already stored in the database");
        }

        requireJDBCConnection();


        // steps:
        // 1) create a program entry in the reasoner_programs table
        // 2) create namespace entries in the reasoner_program_namespaces table
        // 3) create rules in the reasoner_rules table
        // 4) add relation between program and rules to the reasoner_program_rules table


        // 1) create a program entry in the reasoner_programs table

        // get sequence ID
        program.setId(getNextSequence("seq.programs"));

        PreparedStatement insertProgram = getPreparedStatement("programs.insert");
        synchronized (insertProgram) {
            insertProgram.setLong(1,program.getId());
            insertProgram.setString(2, program.getName());
            insertProgram.setString(3, program.getDescription());
            insertProgram.executeUpdate();
            insertProgram.clearParameters();
        }

        // 2) create namespace entries in the reasoner_program_namespaces table
        PreparedStatement insertNamespaces = getPreparedStatement("programs.add_ns");
        synchronized (insertNamespaces) {
            insertNamespaces.clearBatch();
            for(Map.Entry<String,String> entry : program.getNamespaces().entrySet()) {
                insertNamespaces.clearParameters();
                insertNamespaces.setLong(1, program.getId());
                insertNamespaces.setString(2,entry.getKey());
                insertNamespaces.setString(3,entry.getValue());
                insertNamespaces.addBatch();
            }
            insertNamespaces.executeBatch();
            insertNamespaces.clearBatch();
        }


        // 3) create rules in the reasoner_rules table
        for(Rule rule : program.getRules()) {
            storeRule(rule,program.getNamespaces());
        }

        // 4) add relation between program and rules to the reasoner_program_rules table
        PreparedStatement insertRuleRelation = getPreparedStatement("programs.add_rule");
        synchronized (insertRuleRelation) {
            insertRuleRelation.clearBatch();
            for(Rule rule : program.getRules()) {
                insertRuleRelation.clearParameters();
                insertRuleRelation.setLong(1,program.getId());
                insertRuleRelation.setLong(2,rule.getId());
                insertRuleRelation.addBatch();
            }
            insertRuleRelation.executeBatch();
            insertRuleRelation.clearBatch();
        }

        // done
    }


    /**
     * Store the program given as argument in the database. In case the program does not exist yet, it is
     * created together with all rules and namespace definitions. In case the program already exists, it is
     * updated according to the new program specification (i.e. rules and namespaces).
     * <p/>
     * We assume that the program has been newly parsed, so it does not have a database ID yet. To find the
     * existing program in the database, we therefore need to use the program name instead.
     *
     *
     * @param program       the program to update
     * @throws SQLException
     */
    public void updateProgram(Program program) throws SQLException {
        requireJDBCConnection();

        // steps:
        // 1) load old program by name
        // 2) create a diff between old and new program as follows:
        //    2a) check if description has been updated and store it if necessary in the database
        //    2b) check if namespaces have been removed, and remove them if necessary from the database
        //    2c) check if namespaces have been added, and add them if necessary to the database
        //    2d) check if rules have been removed, and remove them if necessary from the database
        //    2e) check if rules have been added, and add them if necessary to the database
        Program old = loadProgram(program.getName());
        if(old == null) {
            storeProgram(program);
        } else {
            //    2a) check if description has been updated and store it if necessary in the database
            if( (old.getDescription() != null && !old.getDescription().equals(program.getDescription())) ||
                    (old.getDescription() == null && program.getDescription() != null)) {
                PreparedStatement updateProgramDescription = getPreparedStatement("programs.update_desc");
                synchronized (updateProgramDescription) {
                    updateProgramDescription.setString(1, program.getDescription());
                    updateProgramDescription.setLong(2, old.getId());
                    updateProgramDescription.executeUpdate();
                }
            }

            //    2b) check if namespaces have been removed, and remove them if necessary from the database
            PreparedStatement deleteProgramNS = getPreparedStatement("programs.delete_ns");
            synchronized (deleteProgramNS) {
                deleteProgramNS.clearBatch();
                for(Map.Entry<String,String> oldNS : old.getNamespaces().entrySet()) {
                    if(!program.getNamespaces().entrySet().contains(oldNS)) {
                        deleteProgramNS.setLong(1,old.getId());
                        deleteProgramNS.setString(2,oldNS.getKey());
                        deleteProgramNS.setString(3,oldNS.getValue());
                        deleteProgramNS.addBatch();
                    }
                }
                deleteProgramNS.executeBatch();
            }

            //    2c) check if namespaces have been added, and add them if necessary to the database
            PreparedStatement addProgramNS = getPreparedStatement("programs.add_ns");
            synchronized (addProgramNS) {
                addProgramNS.clearBatch();
                for(Map.Entry<String,String> newNS : program.getNamespaces().entrySet()) {
                    if(!old.getNamespaces().entrySet().contains(newNS)) {
                        addProgramNS.setLong(1,old.getId());
                        addProgramNS.setString(2, newNS.getKey());
                        addProgramNS.setString(3, newNS.getValue());
                        addProgramNS.addBatch();
                    }
                }
                addProgramNS.executeBatch();
            }

            //    2d) check if rules have been removed, and remove them if necessary from the database
            PreparedStatement deleteProgramRule = getPreparedStatement("programs.delete_rule");
            PreparedStatement deleteRule        = getPreparedStatement("rules.delete_by_id");
            synchronized (deleteProgramRule) {
                deleteProgramRule.clearBatch();
                deleteRule.clearBatch();
                for(Rule oldRule : old.getRules()) {
                    if(!program.getRules().contains(oldRule)) {
                        deleteProgramRule.setLong(1,old.getId());
                        deleteProgramRule.setLong(2,oldRule.getId());
                        deleteProgramRule.addBatch();

                        deleteRule.setLong(1,oldRule.getId());
                        deleteRule.addBatch();

                        deleteJustifications(oldRule);

                        oldRule.setId(null);
                    }
                }
                deleteProgramRule.executeBatch();
                deleteRule.executeBatch();
            }

            //    2e) check if rules have been added, and add them if necessary to the database
            // first create a new entry in the rules table
            PreparedStatement insertRule = getPreparedStatement("rules.insert");
            PreparedStatement addProgramRule = getPreparedStatement("programs.add_rule");
            synchronized (insertRule) {
                insertRule.clearBatch();
                addProgramRule.clearBatch();

                for(Rule rule : program.getRules()) {
                    if(!old.getRules().contains(rule)) {
                        rule.setId(getNextSequence("seq.rules"));

                        insertRule.setLong(1, rule.getId());
                        insertRule.setString(2, rule.getName());
                        insertRule.setString(3, rule.getDescription());
                        insertRule.setString(4, rule.toString(program.getNamespaces()));
                        insertRule.addBatch();

                        addProgramRule.setLong(1,old.getId());
                        addProgramRule.setLong(2,rule.getId());
                        addProgramRule.addBatch();
                    }
                }
                insertRule.executeBatch();
                addProgramRule.executeBatch();
            }
        }


    }


    /**
     * Load and return the program with the name given as argument. Constructing the program requires to invoke
     * the sKWRL parser to parse the textual rule representation.
     *
     *
     * @param name          the name of the program to load
     * @return              the loaded program, or null in case the program does not exist
     * @throws SQLException in case of a database problem or an unparsable program
     */
    public Program loadProgram(String name) throws SQLException {
        requireJDBCConnection();

        PreparedStatement loadProgram = getPreparedStatement("programs.load_by_name");
        synchronized (loadProgram) {
            loadProgram.setString(1, name);
            ResultSet result = loadProgram.executeQuery();
            try {
                if(result.next()) {
                    return constructProgramFromDatabase(result);
                } else {
                    return null;
                }
            } catch (ParseException e) {
                throw new SQLException("error while parsing program rules",e);
            } finally {
                result.close();
            }
        }

    }

    /**
     * Load and return the program with the ID given as argument. Constructing the program requires to invoke
     * the sKWRL parser to parse the textual rule representation.
     *
     * @return
     * @throws SQLException
     */
    public Program loadProgram(Long id) throws SQLException {
        requireJDBCConnection();

        PreparedStatement loadProgram = getPreparedStatement("programs.load_by_id");
        synchronized (loadProgram) {
            loadProgram.setLong(1, id);
            ResultSet result = loadProgram.executeQuery();
            try {
                if(result.next()) {
                    return constructProgramFromDatabase(result);
                } else {
                    return null;
                }
            } catch (ParseException e) {
                throw new SQLException("error while parsing program rules",e);
            } finally {
                result.close();
            }
        }

    }

    protected Program constructProgramFromDatabase(ResultSet row) throws SQLException, ParseException {
        Program program = new Program();
        program.setId(row.getLong("id"));
        program.setName(row.getString("name"));
        program.setDescription(row.getString("description"));

        // load namespaces
        PreparedStatement loadProgramNS = getPreparedStatement("namespaces.load_by_program");
        synchronized (loadProgramNS) {
            loadProgramNS.setLong(1, program.getId());
            ResultSet nsResult = loadProgramNS.executeQuery();
            while(nsResult.next()) {
                program.addNamespace(nsResult.getString("ns_prefix"), nsResult.getString("ns_uri"));
            }
            nsResult.close();
        }

        // load rules
        PreparedStatement loadRule = getPreparedStatement("rules.load_by_program");
        synchronized (loadRule) {
            loadRule.setLong(1,program.getId());
            ResultSet ruleResult = loadRule.executeQuery();
            while(ruleResult.next()) {
                program.addRule(constructRuleFromDatabase(ruleResult,program.getNamespaces()));
            }
            ruleResult.close();
        }

        return program;
    }


    /**
     * List all currently existing programs in the database.
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Program, SQLException> listPrograms() throws SQLException {
        requireJDBCConnection();

        PreparedStatement listPrograms = getPreparedStatement("programs.list");
        synchronized (listPrograms) {
            ResultSet result = listPrograms.executeQuery();

            return new ResultSetIteration<Program>(result, new ResultTransformerFunction<Program>() {
                @Override
                public Program apply(ResultSet row) throws SQLException {
                    try {
                        return constructProgramFromDatabase(row);
                    } catch (ParseException e) {
                        throw new SQLException("error while parsing program rules",e);
                    }
                }
            });
        }
    }


    /**
     * Delete the (persistent) program given as argument from the database, including all rules, justifications and
     * namespaces refenced by the program or its rules.
     *
     * @param program
     * @throws SQLException
     */
    public void deleteProgram(Program program) throws SQLException {
        if(program.getId() == null) {
            log.warn("cannot delete non-persistent program (name={})!", program.getName());
            return;
        }

        requireJDBCConnection();

        // 1. delete all rule associations and rules including the justifications they depend on
        PreparedStatement deleteProgramRule = getPreparedStatement("programs.delete_rule");
        PreparedStatement deleteRule        = getPreparedStatement("rules.delete_by_id");
        synchronized (deleteProgramRule) {
            deleteProgramRule.clearBatch();
            deleteRule.clearBatch();
            for(Rule rule : program.getRules()) {
                deleteProgramRule.setLong(1, program.getId());
                deleteProgramRule.setLong(2, rule.getId());
                deleteProgramRule.addBatch();

                deleteRule.setLong(1, rule.getId());
                deleteRule.addBatch();

                deleteJustifications(rule);
            }
            deleteProgramRule.executeBatch();
            deleteRule.executeBatch();
        }

        // 2. delete all namespaces
        PreparedStatement deleteProgramNS = getPreparedStatement("programs.delete_ns");
        synchronized (deleteProgramNS) {
            deleteProgramNS.clearBatch();
            for(Map.Entry<String,String> ns : program.getNamespaces().entrySet()) {
                deleteProgramNS.setLong(1,   program.getId());
                deleteProgramNS.setString(2, ns.getKey());
                deleteProgramNS.setString(3, ns.getValue());
                deleteProgramNS.addBatch();
            }
            deleteProgramNS.executeBatch();
        }

        // 3. delete program itself
        PreparedStatement deleteProgram = getPreparedStatement("programs.delete");
        synchronized (deleteProgram) {
            deleteProgram.setLong(1, program.getId());
            deleteProgram.executeUpdate();
        }
    }


    /**
     * Store a collection of new justification in the database. Uses an SQL batch operation to speed up
     * database insertion.
     *
     * @param justification
     * @throws SQLException
     */
    public void storeJustifications(Iterable<Justification> justifications) throws SQLException {
        requireJDBCConnection();

        PreparedStatement insertJustification = getPreparedStatement("justifications.insert");
        PreparedStatement justificationAddTriple = getPreparedStatement("justifications.add_triple");
        PreparedStatement justificationAddRule   = getPreparedStatement("justifications.add_rule");

        synchronized (insertJustification) {
            insertJustification.clearBatch();
            justificationAddTriple.clearBatch();
            justificationAddRule.clearBatch();

            for(Justification j : justifications) {
                if(j.getId() != null) {
                    log.warn("justification is already stored in database, not persisting again (database ID: {})", j.getId());
                } else {
                    j.setId(getNextSequence("seq.justifications"));
                    j.setCreatedAt(new Date());

                    // insert an entry to the reasoner_justifications table
                    insertJustification.clearParameters();
                    insertJustification.setLong(1, j.getId());
                    insertJustification.setLong(2, j.getTriple().getId());
                    insertJustification.setTimestamp(3, new Timestamp(j.getCreatedAt().getTime()));
                    insertJustification.addBatch();

                    // insert join entries for all supporting triples
                    for(KiWiTriple supportingTriple : j.getSupportingTriples()) {
                        if(supportingTriple.getId() == null) {
                            log.error("supporting triple is not persistent, cannot store justification (triple={})",supportingTriple);
                        } else {
                            justificationAddTriple.clearParameters();
                            justificationAddTriple.setLong(1, j.getId());
                            justificationAddTriple.setLong(2, supportingTriple.getId());
                            justificationAddTriple.addBatch();
                        }
                    }

                    // insert join entries for all supporting rules
                    for(Rule supportingRule : j.getSupportingRules()) {
                        if(supportingRule.getId() == null) {
                            log.error("supporting rule is not persistent, cannot store justification (rule={})",supportingRule);
                        } else {
                            justificationAddRule.clearParameters();
                            justificationAddRule.setLong(1, j.getId());
                            justificationAddRule.setLong(2, supportingRule.getId());
                            justificationAddRule.addBatch();
                        }
                    }
                }

            }
            insertJustification.executeBatch();
            justificationAddTriple.executeBatch();
            justificationAddRule.executeBatch();

        }
    }


    /**
     * Delete the justifications given as argument (batch operation).
     *
     * @param rule
     * @throws SQLException
     */
    public void deleteJustifications(Iterable<Justification> justifications) throws SQLException {
        deleteJustifications(new IteratorIteration<Justification, SQLException>(justifications.iterator()));
    }


    /**
     * Delete the justifications given as argument (batch operation).
     *
     * @param rule
     * @throws SQLException
     */
    public void deleteJustifications(Iteration<Justification, SQLException> justifications) throws SQLException {
        requireJDBCConnection();

        PreparedStatement deleteJustification = getPreparedStatement("justifications.delete");
        PreparedStatement deleteJustificationRules = getPreparedStatement("justifications.del_rule");
        PreparedStatement deleteJustificationTriples = getPreparedStatement("justifications.del_triple");

        synchronized (deleteJustification) {
            deleteJustification.clearBatch();
            deleteJustificationRules.clearBatch();
            deleteJustificationTriples.clearBatch();

            while(justifications.hasNext()) {
                Justification j = justifications.next();
                if(j.getId() == null) {
                    log.error("cannot delete justification since it does not have a database ID");
                } else {
                    deleteJustificationRules.setLong(1, j.getId());
                    deleteJustificationRules.addBatch();

                    deleteJustificationTriples.setLong(1, j.getId());
                    deleteJustificationTriples.addBatch();

                    deleteJustification.setLong(1, j.getId());
                    deleteJustification.addBatch();
                }
            }
            Iterations.closeCloseable(justifications);

            deleteJustificationTriples.executeBatch();
            deleteJustificationRules.executeBatch();
            deleteJustification.executeBatch();
        }
    }



    /**
     * Delete the justifications referring to a certain rule given as argument.
     *
     * @param rule
     * @throws SQLException
     */
    public void deleteJustifications(Rule rule) throws SQLException {
        deleteJustifications(listJustificationsBySupporting(rule));
    }


    /**
     * Delete the justifications referring to a certain triple given as argument.
     *
     * @param rule
     * @throws SQLException
     */
    public void deleteJustifications(KiWiTriple triple) throws SQLException {
        deleteJustifications(listJustificationsBySupporting(triple));
    }

    /**
     * Delete all justifications.
     *
     * @throws SQLException
     */
    public void deleteJustifications() throws SQLException {
        requireJDBCConnection();

        PreparedStatement deleteJustification = getPreparedStatement("justifications.delete_all");
        PreparedStatement deleteJustificationRules = getPreparedStatement("justifications.delete_all_rules");
        PreparedStatement deleteJustificationTriples = getPreparedStatement("justifications.delete_all_triples");
        synchronized (deleteJustification) {
            deleteJustificationRules.executeUpdate();
            deleteJustificationTriples.executeUpdate();
            deleteJustification.executeUpdate();
        }
    }

    /**
     * List all justifications supported by the given rule.
     *
     * @param rule
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Justification,SQLException> listJustificationsBySupporting(Rule rule) throws SQLException {
        if(rule.getId() == null) {
            return new EmptyIteration<Justification, SQLException>();
        } else {
            requireJDBCConnection();

            PreparedStatement listByRule = getPreparedStatement("justifications.load_by_srule");
            synchronized (listByRule) {
                listByRule.setLong(1, rule.getId());

                ResultSet result = listByRule.executeQuery();

                return new ResultSetIteration<Justification>(result, new ResultTransformerFunction<Justification>() {
                    @Override
                    public Justification apply(ResultSet row) throws SQLException {
                        return constructJustificationFromDatabase(row);
                    }
                });
            }
        }
    }

    /**
     * List all justifications supported by the given triple.
     * @param triple
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Justification,SQLException> listJustificationsBySupporting(KiWiTriple triple) throws SQLException {
        if(triple.getId() == null) {
            return new EmptyIteration<Justification, SQLException>();
        } else {
            requireJDBCConnection();

            PreparedStatement listByTriple = getPreparedStatement("justifications.load_by_striple");
            synchronized (listByTriple) {
                listByTriple.setLong(1, triple.getId());

                ResultSet result = listByTriple.executeQuery();

                return new ResultSetIteration<Justification>(result, new ResultTransformerFunction<Justification>() {
                    @Override
                    public Justification apply(ResultSet row) throws SQLException {
                        return constructJustificationFromDatabase(row);
                    }
                });
            }
        }
    }

    /**
     * List all justifications supporting the given triple.
     * @param triple
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Justification,SQLException> listJustificationsForTriple(KiWiTriple triple) throws SQLException {
        if(triple.getId() == null) {
            return new EmptyIteration<Justification, SQLException>();
        } else {
            return listJustificationsForTriple(triple.getId());
        }
    }

    /**
     * List all justifications supporting the given triple.
     * @param triple
     * @return
     * @throws SQLException
     */
    public CloseableIteration<Justification,SQLException> listJustificationsForTriple(long tripleId) throws SQLException {
        requireJDBCConnection();

        PreparedStatement listForTriple = getPreparedStatement("justifications.load_by_triple");
        synchronized (listForTriple) {
            listForTriple.setLong(1, tripleId);

            ResultSet result = listForTriple.executeQuery();

            return new ResultSetIteration<Justification>(result, new ResultTransformerFunction<Justification>() {
                @Override
                public Justification apply(ResultSet row) throws SQLException {
                    return constructJustificationFromDatabase(row);
                }
            });
        }
    }


    protected Justification constructJustificationFromDatabase(ResultSet row) throws SQLException {
        Justification result = new Justification();
        result.setId(row.getLong("id"));
        result.setTriple(loadTripleById(row.getLong("triple_id")));
        result.setCreatedAt(new Date(row.getTimestamp("createdAt").getTime()));

        // load supporting rules and triples
        PreparedStatement loadRules = getPreparedStatement("justifications.load_rules");
        synchronized (loadRules) {
            loadRules.setLong(1, result.getId());
            ResultSet ruleResult = loadRules.executeQuery();
            while(ruleResult.next()) {
                result.getSupportingRules().add(loadRuleById(ruleResult.getLong("rule_id"),null));
            }
            ruleResult.close();
        }

        PreparedStatement loadTriples = getPreparedStatement("justifications.load_triples");
        synchronized (loadTriples) {
            loadTriples.setLong(1, result.getId());
            ResultSet tripleResult = loadTriples.executeQuery();
            while(tripleResult.next()) {
                result.getSupportingTriples().add(loadTripleById(tripleResult.getLong("triple_id")));
            }
            tripleResult.close();
        }

        return result;
    }

    /**
     * List all triples that are not supported by at least one justification.
     * @return
     * @throws SQLException
     */
    public CloseableIteration<KiWiTriple, SQLException> listUnsupportedTriples() throws SQLException {
        requireJDBCConnection();

        PreparedStatement listUnsupported = getPreparedStatement("justifications.list_unsupported");
        synchronized (listUnsupported) {
            ResultSet result = listUnsupported.executeQuery();

            return new ResultSetIteration<KiWiTriple>(result, new ResultTransformerFunction<KiWiTriple>() {
                @Override
                public KiWiTriple apply(ResultSet row) throws SQLException {
                    return constructTripleFromDatabase(row);
                }
            });
        }

    }



    /**
     * Evaluate a query on the triple store. The query parameters passed to the method call are
     * translated into database queries in HQL and directly evaluated by a single database
     * query that yields the query bindings.
     * <p/>
     * The query patterns may share variable fields, in which case the database query will evaluate
     * a join. In case the optional parameter is true, the database query will succeed even if the query
     * does not yield a value.
     *
     * @param patterns        the set of patterns to query; patterns are considered to be connected by AND;
     *                        occurrences of the same variable in multiple patterns will be evaluated as a join
     * @param initialBindings initial bindings of variable fields may be used in case some bindings are
     *                        already available to further restrict the results; the purpose of this parameter
     *                        is to support query-by-example queries as well as to speed up e.g. incremental
     *                        reasoning
     * @param filters         a set of filters to apply to the result before returning the bindings; depending
     *                        on the kind of filter, filtering will be carried out by the database or in memory
     * @param orderBy         list of variables by whose bindings the result rows should be ordered; variables
     *                        at the beginning of the list take precedence over variables that are further behind
     * @param optional        allow variables to be unbound; if this parameter is set, at least one variable
     *                        needs to give a result
     * @return a list of bindings matching the query patterns and filters, ordered by the specified
     *         variables and offset and limited by the parameters given
     */
    public CloseableIteration<QueryResult, SQLException> query(final Collection<Pattern> patterns, final QueryResult initialBindings, Set<Filter> filters, List<VariableField> orderBy, final boolean justifications) throws SQLException {
        requireJDBCConnection();

        if(filters != null) {
            throw new IllegalArgumentException("filters are not yet supported by the QueryService");
        }

        // some definitions
        String[] positions = new String[] {"subject","predicate","object","context"};


        // associate a name with each pattern; the names are used in the database query to refer to the triple
        // that matched this pattern and in the construction of variable names for the HQL query
        int patternCount = 0;
        final Map<Pattern,String> patternNames = new HashMap<Pattern, String>();
        for(Pattern p : patterns) {
            patternNames.put(p,"P"+ (++patternCount));
        }

        // find all variables occurring in the patterns and create a map to map them to
        // field names in the database query; each variable will have one or several field names,
        // one for each pattern it occurs in; field names are constructed automatically by a counter
        // and the pattern name to ensure the name is a valid HQL identifier
        int variableCount = 0;

        // a map for the variable names; will look like { ?x -> "V1", ?y -> "V2", ... }
        final Map<VariableField,String> variableNames = new HashMap<VariableField, String>();

        // a map for mapping variables to field names; each variable might have one or more field names,
        // depending on the number of patterns it occurs in; will look like
        // { ?x -> ["P1_V1", "P2_V1"], ?y -> ["P2_V2"], ... }
        Map<VariableField,List<String>> queryVariables = new HashMap<VariableField, List<String>>();
        for(Pattern p : patterns) {
            Field[] fields = new Field[] {
                    p.getSubject(),
                    p.getProperty(),
                    p.getObject(),
                    p.getContext()
            };
            for(int i = 0; i<fields.length; i++) {
                if(fields[i] != null && fields[i].isVariableField()) {
                    VariableField v = (VariableField)fields[i];
                    if(variableNames.get(v) == null) {
                        variableNames.put(v,"V"+ (++variableCount));
                        queryVariables.put(v,new LinkedList<String>());
                    }
                    String pName = patternNames.get(p);
                    String vName = variableNames.get(v);
                    queryVariables.get(v).add(pName + "_" + positions[i] + "_" + vName);
                }
            }
        }

        // build the select clause by projecting for each query variable the first name
        StringBuilder selectClause = new StringBuilder();
        final List<VariableField> selectVariables = new LinkedList<VariableField>();
        for(Iterator<VariableField> it = queryVariables.keySet().iterator(); it.hasNext(); ) {
            VariableField v = it.next();
            String projectedName = variableNames.get(v);
            String fromName = queryVariables.get(v).get(0);
            selectClause.append(fromName);
            selectClause.append(".id as ");
            selectClause.append(projectedName);
            if(it.hasNext()) {
                selectClause.append(", ");
            }
            selectVariables.add(v);
        }
        if(justifications) {
            // project also the ids of triples that have matched; we use it for building justifications
            for(Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {
                Pattern p = it.next();
                if(selectClause.length() > 0) {
                    selectClause.append(", ");
                }
                selectClause.append(patternNames.get(p));
                selectClause.append(".id as ");
                selectClause.append(patternNames.get(p));
            }
        }



        // build the from-clause of the query; the from clause is constructed as follows:
        // 1. for each pattern P, there will be a "KiWiTriple P" in the from clause
        // 2. for each variable V in P occurring in
        //    - subject, there will be a "inner join P.subject as P_S_V" or "left outer join P.subject as P_S_V",
        //      depending on whether the "optional" parameter is false or true
        //    - property, there will be a "inner join P.property as P_P_V" or "left outer join p.property as P_P_V"
        //    - object, there will be a "inner join P.object as P_O_V" or "left outer join p.object as P_O_V"
        //    - context, there will be a "inner join P.context as P_C_V" or "left outer join p.context as P_C_V"
        StringBuilder fromClause = new StringBuilder();
        for(Iterator<Pattern> it = patterns.iterator(); it.hasNext(); ) {
            Pattern p = it.next();
            String pName = patternNames.get(p);
            fromClause.append("triples "+pName);

            Field[] fields = new Field[] {
                    p.getSubject(),
                    p.getProperty(),
                    p.getObject(),
                    p.getContext()
            };
            for(int i = 0; i<fields.length; i++) {
                if(fields[i] != null && fields[i].isVariableField()) {
                    String vName = variableNames.get(fields[i]);
                    fromClause.append(" INNER JOIN nodes AS ");
                    fromClause.append(pName + "_"+positions[i]+"_" + vName);
                    fromClause.append(" ON " + pName + "." + positions[i] + " = ");
                    fromClause.append(pName + "_"+positions[i]+"_" + vName + ".id ");
                }
            }

            if(it.hasNext()) {
                fromClause.append(",\n ");
            }
        }


        // build the where clause as follows:
        // 1. iterate over all patterns and for each resource and literal field in subject,
        //    property, object, or context, and set a query condition according to the
        //    nodes given in the pattern
        // 2. for each variable that has more than one occurrences, add a join condition
        // 3. for each variable in the initialBindings, add a condition to the where clause

        // list of where conditions that will later be connected by AND
        List<String> whereConditions = new LinkedList<String>();


        // 1. iterate over all patterns and for each resource and literal field in subject,
        //    property, object, or context, and set a query condition according to the
        //    nodes given in the pattern
        for(Pattern p : patterns) {
            String pName = patternNames.get(p);
            Field[] fields = new Field[] {
                    p.getSubject(),
                    p.getProperty(),
                    p.getObject(),
                    p.getContext()
            };
            for(int i = 0; i<fields.length; i++) {
                // find node id of the resource or literal field and use it in the where clause
                // in this way we can avoid setting too many query parameters
                Long nodeId = null;
                if(fields[i] != null && fields[i].isLiteralField()) {
                    nodeId = ((KiWiNode)((LiteralField)fields[i]).getLiteral()).getId();
                } else if(fields[i] != null && fields[i].isResourceField()) {
                    nodeId = ((KiWiNode)((ResourceField)fields[i]).getResource()).getId();
                }

                if(nodeId != null) {
                    String condition = pName+"."+positions[i]+" = " + nodeId;
                    whereConditions.add(condition);
                }
            }
        }

        // 2. for each variable that has more than one occurrences, add a join condition
        for(VariableField v : queryVariables.keySet()) {
            List<String> vNames = queryVariables.get(v);
            for(int i = 1; i < vNames.size(); i++) {
                String vName1 = vNames.get(i-1);
                String vName2 = vNames.get(i);
                whereConditions.add(vName1 + ".id = " + vName2 + ".id");
            }
        }

        // 3. for each variable in the initialBindings, add a condition to the where clause setting it
        //    to the node given as binding
        if(initialBindings != null) {
            for(VariableField v : initialBindings.getBindings().keySet()) {
                List<String> vNames = queryVariables.get(v);
                if(vNames != null && vNames.size() > 0) {
                    String vName = vNames.get(0);
                    KiWiNode binding = initialBindings.getBindings().get(v);
                    whereConditions.add(vName+".id = "+binding.getId());
                }
            }
        }

        // 4. for each pattern, ensure that the matched triple is not marked as deleted
        for(Pattern p : patterns) {
            String pName = patternNames.get(p);
            whereConditions.add(pName+".deleted = false");
        }

        // construct the where clause
        StringBuilder whereClause = new StringBuilder();
        for(Iterator<String> it = whereConditions.iterator(); it.hasNext(); ) {
            whereClause.append(it.next());
            whereClause.append("\n ");
            if(it.hasNext()) {
                whereClause.append("AND ");
            }
        }


        // finally, construct the order by clause
        StringBuilder orderByClause = new StringBuilder();
        if(orderBy != null && orderBy.size() > 0) {
            for(Iterator<VariableField> it = orderBy.iterator(); it.hasNext(); ) {
                VariableField v = it.next();
                String vName = variableNames.get(v);
                orderByClause.append(vName);
                if(it.hasNext()) {
                    orderByClause.append(", ");
                }
            }
        }

        // build the query string
        String queryString =
                "SELECT " + selectClause + "\n " +
                        "FROM " + fromClause + "\n " +
                        "WHERE " + whereClause + "\n " +
                        ((orderBy != null && orderBy.size() > 0)?"ORDER BY "+orderByClause+" ASC\n ":"");

        log.debug("constructed SQL query string {}",queryString);

        PreparedStatement queryStatement = getJDBCConnection().prepareStatement(queryString);
        ResultSet result = queryStatement.executeQuery();

        return new ResultSetIteration<QueryResult>(result, true, new ResultTransformerFunction<QueryResult>() {
            @Override
            public QueryResult apply(ResultSet row) throws SQLException {
                QueryResult resultRow = new QueryResult();

                for(VariableField v : selectVariables) {
                    resultRow.getBindings().put(v, loadNodeById(row.getLong(variableNames.get(v))));
                }

                if(justifications) {
                    for(Pattern p : patterns) {
                        resultRow.getJustifications().add(loadTripleById(row.getLong(patternNames.get(p))));
                    }
                }

                if(initialBindings != null && initialBindings.getBindings().size() > 0) {
                    for(VariableField v : initialBindings.getBindings().keySet()) {
                        if(!resultRow.getBindings().containsKey(v)) {
                            resultRow.getBindings().put(v,initialBindings.getBindings().get(v));
                        }
                    }
                    if(justifications) {
                        resultRow.getJustifications().addAll(initialBindings.getJustifications());
                    }
                }
                return resultRow;
            }
        });
    }


}
TOP

Related Classes of org.apache.marmotta.kiwi.reasoner.persistence.KiWiReasoningConnection

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.