net.antidot.semantic.rdf.rdb2rdf.dm.core.DirectMappingEngineWD20120529.java Source code

Java tutorial

Introduction

Here is the source code for net.antidot.semantic.rdf.rdb2rdf.dm.core.DirectMappingEngineWD20120529.java

Source

/* 
 * Copyright 2011-2013 Antidot opensource@antidot.net
 * https://github.com/antidot/db2triples
 * 
 * This file is part of DB2Triples
 *
 * DB2Triples is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * DB2Triples is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/***************************************************************************
 *
 * Direct Mapping : Direct Mapper Engine of Working Draft 20 September 2011
 *
 * This Direct Mapper Engine implements functions and mecanisms of this working draft.
 * 
 *
 ****************************************************************************/
package net.antidot.semantic.rdf.rdb2rdf.dm.core;

import java.io.UnsupportedEncodingException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.TreeMap;

import net.antidot.semantic.rdf.model.impl.sesame.SemiStatement;
import net.antidot.semantic.rdf.model.tools.RDFDataValidator;
import net.antidot.semantic.rdf.rdb2rdf.commons.RDFPrefixes;
import net.antidot.semantic.rdf.rdb2rdf.commons.SQLToXMLS;
import net.antidot.semantic.rdf.rdb2rdf.r2rml.core.R2RMLProcessor;
import net.antidot.semantic.rdf.rdb2rdf.r2rml.tools.R2RMLToolkit;
import net.antidot.semantic.xmls.xsd.XSDLexicalTransformation;
import net.antidot.semantic.xmls.xsd.DataType;
import net.antidot.sql.model.core.DriverType;
import net.antidot.sql.model.db.CandidateKey;
import net.antidot.sql.model.db.ForeignKey;
import net.antidot.sql.model.db.Key;
import net.antidot.sql.model.db.Row;
import net.antidot.sql.model.db.StdBody;
import net.antidot.sql.model.db.StdHeader;
import net.antidot.sql.model.db.StdTable;
import net.antidot.sql.model.db.Tuple;
import net.antidot.sql.model.type.SQLType;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;

public class DirectMappingEngineWD20120529 implements DirectMappingEngine {

    // Log
    private static Log log = LogFactory.getLog(DirectMappingEngineWD20120529.class);

    // Database values
    private String currentTableName;
    private LinkedHashMap<String, String> datatypes;
    private StdHeader header;
    private ArrayList<CandidateKey> primaryKeys;
    private HashSet<ForeignKey> foreignKeys;

    // Sesame valueFactory which generates values like litterals or URI
    private ValueFactory vf;

    // IRI characters used in rules
    private static char solidus = '/';
    private static char semicolon = ';';
    private static char hash = '#';
    private static char hyphenEquals = '=';
    private static String refInfix = "ref-";

    public DirectMappingEngineWD20120529() {
        initDatabaseValues();
    }

    private void initDatabaseValues() {
        currentTableName = null;
        datatypes = new LinkedHashMap<String, String>();
        header = null;
        primaryKeys = new ArrayList<CandidateKey>();
        foreignKeys = new HashSet<ForeignKey>();
        vf = new ValueFactoryImpl();
    }

    /*
     * Extract a tuple from database extracted sets.
     */
    public Tuple extractTupleFrom(ResultSet valueSet, ResultSet headerSet, ResultSet primaryKeysSet,
            ResultSet foreignKeysSet, String tableName, DriverType driver, String timeZone, int index)
            throws UnsupportedEncodingException {
        if (tableName != currentTableName)
            // First table treatment
            // Get datatypes
            updateTableContext(headerSet, primaryKeysSet, foreignKeysSet, tableName);
        if (datatypes == null || header == null || primaryKeys == null || foreignKeys == null)
            throw new IllegalStateException(
                    "[DirectMappingEngine:extractTupleFrom] One of mandatory elements for tuple's building is missing.");
        // Extract row
        Row result = extractRow(driver, header, tableName, valueSet, timeZone, index);
        // Attach context to this row
        buildTmpModel(result, tableName, header, primaryKeys, foreignKeys);
        return result;
    }

    /*
     * Update data associated with current table.
     */
    private void updateTableContext(ResultSet headerSet, ResultSet primaryKeysSet, ResultSet foreignKeysSet,
            String tableName) {
        // First table treatment
        // Get datatypes
        datatypes = extractDatatypes(headerSet, tableName);
        header = new StdHeader(datatypes);
        // Extract candidate keys
        primaryKeys = extractPrimaryKeys(primaryKeysSet, header, tableName);
        // Extract foreign key
        if (foreignKeysSet != null)
            foreignKeys = extractForeignKeys(foreignKeysSet, tableName);
    }

    /*
     * Extract a referenced tuple (tuple pointed by a foreign key) from database
     * extracted sets.
     */
    public Tuple extractReferencedTupleFrom(ResultSet valueSet, ResultSet headerSet, ResultSet primaryKeysSet,
            ResultSet foreignKeysSet, String tableName, DriverType driver, String timeZone, int index)
            throws UnsupportedEncodingException {
        // Get datatypes
        LinkedHashMap<String, String> referencedDatatypes = extractDatatypes(headerSet, tableName);
        StdHeader referencedHeader = new StdHeader(referencedDatatypes);
        // Extract candidate keys
        ArrayList<CandidateKey> referencedPrimaryKeys = extractPrimaryKeys(primaryKeysSet, referencedHeader,
                tableName);
        // Extract foreign key
        HashSet<ForeignKey> referencedForeignKeys = extractForeignKeys(foreignKeysSet, tableName);
        if (datatypes == null || header == null || primaryKeys == null)
            throw new IllegalStateException(
                    "[DirectMappingEngine:extractTupleFrom] One of mandatory elements for tuple's building is missing.");
        // Extract row
        Row result = extractRow(driver, referencedHeader, tableName, valueSet, timeZone, index);
        // Attach context to this row
        buildTmpModel(result, tableName, referencedHeader, referencedPrimaryKeys, referencedForeignKeys);
        return result;
    }

    /*
     * Build objects associated with a row from database extracted sets.
     */
    private void buildTmpModel(Row row, String tableName, StdHeader header, ArrayList<CandidateKey> primaryKeys,
            HashSet<ForeignKey> foreignKeys) {
        // Create body
        HashSet<Row> rows = new HashSet<Row>();
        rows.add(row);
        StdBody body = new StdBody(rows, null);
        // Create table
        StdTable table = new StdTable(tableName, header, primaryKeys, foreignKeys, body);
        // Link objects
        body.setParentTable(table);
        row.setParentBody(body);
    }

    /*
     * Extract a row from values datasets and its model.
     */
    private Row extractRow(DriverType driver, StdHeader header, String tableName, ResultSet valueSet,
            String timeZone, int index) throws UnsupportedEncodingException {
        TreeMap<String, byte[]> values = new TreeMap<String, byte[]>();
        for (String columnName : header.getColumnNames()) {
            try {
                byte[] value = null;
                // SQLType type =
                // SQLType.toSQLType(Integer.valueOf(header.getDatatypes().get(columnName)));
                value = valueSet.getBytes(columnName);

                // http://bugs.mysql.com/bug.php?id=65943
                if (value != null && driver.equals(DriverType.MysqlDriver) && SQLType.toSQLType(
                        valueSet.getMetaData().getColumnType(valueSet.findColumn(columnName))) == SQLType.CHAR) {
                    value = valueSet.getString(columnName).getBytes();
                }

                values.put(columnName, value);
            } catch (SQLException e) {
                log.error("[DirectMappingEngine:extractRow] SQL Error during row extraction");
                e.printStackTrace();
            }
        }
        Row row = new Row(values, null, index);
        return row;
    }

    /*
     * Extract datatypes from a table and its header set.
     */
    private LinkedHashMap<String, String> extractDatatypes(ResultSet headersSet, String tableName) {
        LinkedHashMap<String, String> datatypes = new LinkedHashMap<String, String>();
        try {
            while (headersSet.next()) {
                String type = headersSet.getString("DATA_TYPE");
                String column = headersSet.getString("COLUMN_NAME");
                checkBlobType(type, tableName, column);
                datatypes.put(column, type);
            }
        } catch (SQLException e) {
            log.error("[DirectMappingEngine:extractDatatypes] SQL Error during datatype's tuples extraction");
            e.printStackTrace();
        }
        return datatypes;
    }

    /*
     * Check blob types. This type is not supported in this working draft.
     */
    private void checkBlobType(String type, String tableName, String column) {
        if (SQLType.toSQLType(Integer.valueOf(type)).isBlobType())
            log.warn("[DirectMapper:checkBlobType] WARNING Table " + tableName + ", column " + column
                    + " Forbidden BLOB type (binary stream not supported in XSD)"
                    + " => this column will be ignored.");
    }

    /*
     * Extract primary keys from database sets.
     */
    private ArrayList<CandidateKey> extractPrimaryKeys(ResultSet primaryKeysSet, StdHeader header,
            String tableName) {
        ArrayList<CandidateKey> primaryKeys = new ArrayList<CandidateKey>();
        // In particular : primary key

        ArrayList<String> columnNames = new ArrayList<String>();
        int size = 0;
        // Extract columns names
        try {
            while (primaryKeysSet.next()) {
                size++;
                String columnName = primaryKeysSet.getString("COLUMN_NAME");
                columnNames.add(columnName);
            }
        } catch (SQLException e) {
            log.error("[DirectMappingEngine:extractPrimaryKeys] SQL Error during primary key of tuples extraction");
            e.printStackTrace();
        }
        // Sort columns
        ArrayList<String> sortedColumnNames = new ArrayList<String>();
        for (String columnName : header.getColumnNames()) {
            if (columnNames.contains(columnName))
                sortedColumnNames.add(columnName);
        }
        // Create object
        if (size != 0) {
            CandidateKey primaryKey = new CandidateKey(sortedColumnNames, tableName, CandidateKey.KeyType.PRIMARY);
            primaryKeys.add(primaryKey);
        }
        if (primaryKeys.size() > 1)
            throw new IllegalStateException(
                    "[DirectMappingEngine:extractPrimaryKeys] Table " + tableName + " has more primary keys.");
        return primaryKeys;
    }

    /*
     * Extract foreign keys from database sets.
     */
    private HashSet<ForeignKey> extractForeignKeys(ResultSet foreignKeysSet, String tableName) {
        // Extract foreign key
        log.error("[DirectMappingEngine:extractForeignKeys] Extract Foreign Keys for : " + tableName);
        HashSet<ForeignKey> foreignKeys = new HashSet<ForeignKey>();
        String currentPkTableName = null;
        ArrayList<String> pkColumnNames = new ArrayList<String>();
        ArrayList<String> fkColumnNames = new ArrayList<String>();
        try {
            while (foreignKeysSet.next()) {
                String pkTableName;
                // Foreign key infos
                pkTableName = foreignKeysSet.getString("PKTABLE_NAME");

                String pkColumnName = foreignKeysSet.getString("PKCOLUMN_NAME");
                String fkTableName = foreignKeysSet.getString("FKTABLE_NAME");
                String fkColumnName = foreignKeysSet.getString("FKCOLUMN_NAME");
                int fkSequence = foreignKeysSet.getInt("KEY_SEQ");
                // Consistency test
                if (!fkTableName.equals(tableName))
                    throw new IllegalStateException(
                            "[DirectMappingEngine:extractForeignKeys] Unconsistency between source "
                                    + "table of foreign key and current table : " + tableName + " != "
                                    + fkTableName);

                if (fkSequence == 1) { // Sequence == order of column in
                    // Multi-column foreign key
                    // New foreign key => store last key
                    storeForeignKey(foreignKeys, fkColumnNames, pkColumnNames, tableName, currentPkTableName);
                    fkColumnNames = new ArrayList<String>();
                    pkColumnNames = new ArrayList<String>();
                }
                currentPkTableName = pkTableName;
                pkColumnNames.add(pkColumnName);
                fkColumnNames.add(fkColumnName);
            }
            log.error("[DirectMappingEngine:extractForeignKeys] Store now last foreign key");
            // Store last key
            storeForeignKey(foreignKeys, fkColumnNames, pkColumnNames, tableName, currentPkTableName);

        } catch (SQLException e) {
            log.error(
                    "[DirectMappingEngine:extractForeignKeys] SQL Error during foreign keys of tuples extraction");
            e.printStackTrace();
        }
        return foreignKeys;
    }

    /*
     * Create foreign key object.
     */
    private void storeForeignKey(HashSet<ForeignKey> foreignKeys, ArrayList<String> fkColumnNames,
            ArrayList<String> pkColumnNames, String tableName, String currentPkTableName) {
        log.debug("[DirectMappingEngine:storeForeignKey] Store foreign key : " + pkColumnNames);
        if (fkColumnNames.size() != 0)
            foreignKeys.add(new ForeignKey(fkColumnNames, tableName,
                    new CandidateKey(pkColumnNames, currentPkTableName, CandidateKey.KeyType.REFERENCE)));
    }

    /*
     * Construct SQL Query from database sets in order to extract row from its
     * table.
     */
    public String constructSQLQuery(DriverType driver, ResultSet headersSet, String tableName) {
        LinkedHashMap<String, String> datatypes = extractDatatypes(headersSet, tableName);
        StdHeader header = new StdHeader(datatypes);
        // Construct SQL query
        String SQLQuery = "SELECT ";
        int i = 0;
        for (String columnName : header.getColumnNames()) {
            i++;
            // Extract SQL date format in a ISO 8601 format
            SQLType type = SQLType.toSQLType(Integer.valueOf(header.getDatatypes().get(columnName)));
            if (type == null) {
                throw new IllegalStateException("[DirectMappingEngine:constructSQLQuery] Unknown SQL type : "
                        + header.getDatatypes().get(columnName) + " from column : " + columnName);
            }
            if (type == SQLType.UNKNOWN) {
                log.warn("[DirectMappingEngine:constructSQLQuery] Unknown SQL type : "
                        + header.getDatatypes().get(columnName) + " from column : " + columnName);
            }
            if (driver.equals(DriverType.MysqlDriver))
                SQLQuery += "`" + columnName + "`";
            else
                SQLQuery += "\"" + columnName + "\"";
            if (i < header.getColumnNames().size())
                SQLQuery += ", ";
        }
        if (driver.equals(DriverType.MysqlDriver))
            SQLQuery += " FROM `" + tableName + "`;";
        else
            SQLQuery += " FROM \"" + tableName + "\";";
        return SQLQuery;
    }

    /*
     * Construct SQL Query from database sets in order to extract row from its
     * table.
     */
    public String constructReferencedSQLQuery(DriverType driver, ResultSet headersSet, String tableName, Key key,
            Tuple tuple) {
        // Explicit conversion
        ForeignKey fk = (ForeignKey) key;
        Row r = (Row) tuple;
        LinkedHashMap<String, String> datatypes = extractDatatypes(headersSet, tableName);
        StdHeader header = new StdHeader(datatypes);
        // Construct SQL query
        // SELECT clause
        String SQLQuery = "SELECT ";
        int i = 0;
        for (String columnName : header.getColumnNames()) {
            i++;
            // Extract SQL date format in a ISO 8601 format
            SQLType type = SQLType.toSQLType(Integer.valueOf(header.getDatatypes().get(columnName)));
            if (type == null) {
                throw new IllegalStateException("[DirectMappingEngine:constructSQLQuery] Unknown SQL type : "
                        + header.getDatatypes().get(columnName) + " from column : " + columnName);
            }
            if (type == SQLType.UNKNOWN) {
                if (log.isWarnEnabled())
                    log.warn("[DirectMappingEngine:constructSQLQuery] Unknown SQL type : "
                            + header.getDatatypes().get(columnName) + " from column : " + columnName);
            }
            if (driver.equals(DriverType.MysqlDriver))
                SQLQuery += "`" + columnName + "`";
            else
                SQLQuery += "\"" + columnName + "\"";
            if (i < header.getColumnNames().size())
                SQLQuery += ", ";
        }
        if (driver.equals(DriverType.MysqlDriver))
            SQLQuery += " FROM `" + tableName + "`";
        else
            SQLQuery += " FROM \"" + tableName + "\"";
        // WHERE clause
        SQLQuery += " WHERE ";
        int j = 0;
        ArrayList<String> columnNames = fk.getReferenceKey().getColumnNames();
        for (String columnName : columnNames) {
            String finalColumnName = "\"" + columnName + "\"";
            if (driver.equals(DriverType.MysqlDriver)) {
                finalColumnName = "`" + columnName + "`";
            }

            final byte[] bs = r.getValues().get(fk.getColumnNames().get(j));
            if (bs == null) {
                // Always use IS NULL to look for NULL values.
                SQLQuery += finalColumnName + " IS NULL";
            } else {
                String value = new String(bs);
                SQLQuery += finalColumnName + " = '" + value + "'";
            }

            j++;
            if (j < columnNames.size())
                SQLQuery += " AND ";
            else
                SQLQuery += ";";

        }
        return SQLQuery;
    }

    /*
     * Most of the functions defining the Direct Mapping are higher-order
     * functions parameterized by a function  (phi) : Row  Node. This function
     * maps any row to a unique node IRI or Blank Node [35]/[36].
     * 
     * @throws UnsupportedEncodingException
     */
    private Resource phi(StdTable t, Row r, String baseURI) throws UnsupportedEncodingException {
        if (log.isDebugEnabled())
            log.debug("[DirectMappingEngine:phi] Table : " + t);
        CandidateKey primaryKey = t.getPrimaryKey();
        if (primaryKey != null) {
            // Unique Node IRI
            String stringURI = generateUniqNodeIRI(r, t, primaryKey, baseURI);
            URI uri = vf.createURI(baseURI, stringURI);
            return uri;
        } else {
            // Blank node
            BNode bnode = vf.createBNode(generateUniqBlankNodeName(r));
            return bnode;
        }
    }

    /*
     * Phi function for referenced rows (in particular for blank node mangement)
     */
    private Resource phi(StdTable t, Row row, Row referencedRow, String baseURI)
            throws UnsupportedEncodingException {
        log.debug("[DirectMappingEngine:phi] Table : " + t);

        CandidateKey primaryKey = t.getPrimaryKey();
        if (primaryKey != null) {
            // Unique Node IRI
            String stringURI = generateUniqNodeIRI(referencedRow, t, primaryKey, baseURI);

            URI uri = vf.createURI(baseURI, stringURI);
            // URIs.put(r, uri);
            return uri;
        } else {
            // Blank node
            BNode bnode = vf.createBNode(generateUniqBlankNodeName(referencedRow));
            return bnode;
        }
    }

    /*
     * Percent encode
     */
    private String percentEncode(String value, boolean isAttributeName, boolean isAttributeValue) {
        // Replace the string with the IRI-safe form of that character per
        // section 7.3 of [R2RML].
        String result = R2RMLToolkit.getIRISafeVersion(value);
        // For attribute names, replace each HYPHEN-MINUS character ('-',
        // U+003d) with the string "%3D".
        if (isAttributeName)
            result = result.replaceAll("\\-", "%3D");
        // For attribute values, replace each FULL STOP character ('.', U+002e)
        // with the string "%2E".
        if (isAttributeValue)
            result = result.replaceAll("\\-", "%2E");
        return result;
    }

    /*
     * Generate IRI name from database information.
     */
    private String generateUniqNodeIRI(Row r, StdTable t, CandidateKey primaryKey, String baseURI)
            throws UnsupportedEncodingException {
        String stringURI = percentEncode(t.getTableName(), false, false) + solidus;
        int i = 0;
        for (String columnName : primaryKey.getColumnNames()) {
            i++;
            final byte[] bs = r.getValues().get(columnName);
            stringURI += percentEncode(columnName, true, false) + hyphenEquals
                    + percentEncode(new String(bs), false, true);
            if (i < primaryKey.getColumnNames().size())
                stringURI += semicolon;
        }
        // Check URI syntax
        if (!RDFDataValidator.isValidURI(baseURI + stringURI)) {
            if (log.isWarnEnabled())
                log.warn("[DirectMappingEngine:phi] This URI is not valid : " + baseURI + stringURI);
        }
        return stringURI;
    }

    /*
     * Generate blank name (useful for link row and referenced key in case of
     * empty primary key).
     */
    private String generateUniqBlankNodeName(Row r) throws UnsupportedEncodingException {
        String blankNodeUniqName = r.getIndex() + "-";
        int i = 1;
        for (String columnName : r.getValues().keySet()) {
            final byte[] bs = r.getValues().get(columnName);
            blankNodeUniqName += percentEncode(columnName, true, false) + hyphenEquals
                    + percentEncode(new String(bs), false, true);
            if (i < r.getValues().size())
                blankNodeUniqName += semicolon;
            i++;
        }
        // Bug of Jena......
        return blankNodeUniqName.replace("%", "P").replace(";", "S").replace("=", "-");
    }

    /*
     * Primary-is-Candidate-Key Exception If the primary key is also a candidate
     * key K to table R : - The shared subject is the subject of the referenced
     * row in R. - The foreign key K generates no reference triple. - Even if K
     * is a single-column foreign key, it generates a literal triple.
     */
    private HashSet<Statement> convertPrimaryIsCandidateKey(Row row, Row referencedRow, ForeignKey fk,
            String baseURI) throws UnsupportedEncodingException {
        HashSet<Statement> result = new HashSet<Statement>();
        // Generate URI subject

        String saveTableName = referencedRow.getParentBody().getParentTable().getTableName();
        StdTable referencedTable = referencedRow.getParentBody().getParentTable();
        referencedTable.setTableName(fk.getTargetTableName());

        Resource s = phi(referencedTable, referencedRow, baseURI);
        referencedTable.setTableName(saveTableName);
        // Generate predicates and objects
        for (String columnName : fk.getColumnNames()) {
            // For each column in candidate key, a literal triple is generated
            Statement t = convertLex(s, row.getParentBody().getParentTable().getHeader(), row, columnName, baseURI);
            result.add(t);
        }
        return result;
    }

    /*
     * Denotational semantics function : convert foreign key columns into a
     * triple with mapped (predicate, object).
     */
    private HashSet<SemiStatement> convertRef(Row row, Row referencedRow, ForeignKey fk, String baseURI)
            throws UnsupportedEncodingException {
        log.debug("[DirectMappingEngine:convertRef] Row : " + row + " Referenced row : " + referencedRow);
        HashSet<SemiStatement> result = new HashSet<SemiStatement>();
        ArrayList<String> columnNames = new ArrayList<String>();
        columnNames.addAll(fk.getColumnNames());
        URI p = convertCol(row, columnNames, baseURI, true);

        // Do NOT build if ANY ref is "null"
        for (String colName : fk.getReferenceKey().getColumnNames()) {
            if (referencedRow.getValues().get(colName) == null) {
                log.debug("[DirectMappingEngine:convertRef] Return since " + colName + " is null");
                return result;
            }
        }
        // Get URI of target table
        Resource o = phi(referencedRow.getParentBody().getParentTable(), row, referencedRow, baseURI);
        SemiStatement semiTriple = new SemiStatement(p, o);
        result.add(semiTriple);

        return result;
    }

    /*
     * Denotational semantics function : convert row into triples.
     */
    public HashSet<Statement> extractTriplesFrom(Tuple t, HashMap<Key, Tuple> referencedTuples,
            Key primaryIsForeignKey, String baseURI) throws UnsupportedEncodingException {
        log.debug("[DirectMappingEngine:extractTriplesFrom] Tuple : " + t + ", referencedTuples : "
                + referencedTuples);
        // Explicit conversion
        Row r = (Row) t;
        HashMap<ForeignKey, Row> referencedRows = new HashMap<ForeignKey, Row>();
        for (Key key : referencedTuples.keySet()) {
            referencedRows.put((ForeignKey) key, (Row) referencedTuples.get(key));
        }
        ForeignKey primaryIsFk = (ForeignKey) primaryIsForeignKey;
        HashSet<Statement> result = new HashSet<Statement>();
        StdTable currentTable = r.getParentBody().getParentTable();

        Resource s = phi(currentTable, r, baseURI);
        // Temporary set of triple used to store triples
        // before attribute them their subject
        // It's necessary for manage the Primary-is-Candidate-Key Exception
        // which can modify subject
        HashSet<SemiStatement> tmpResult = new HashSet<SemiStatement>();
        if (primaryIsForeignKey != null) {
            // Primary-is-Candidate-Key Exception
            HashSet<Statement> primaryIsCandidateKeyTriples = convertPrimaryIsCandidateKey(r, r, primaryIsFk,
                    baseURI);
            // Add these triples to result
            boolean firstTriple = true;
            for (Statement primaryIsCandidateKeyTriple : primaryIsCandidateKeyTriples) {
                // Modify subject
                if (firstTriple) {
                    s = primaryIsCandidateKeyTriple.getSubject();
                    firstTriple = false;
                }
                result.add(primaryIsCandidateKeyTriple);
            }
        }
        for (ForeignKey fk : referencedRows.keySet()) {
            HashSet<SemiStatement> refTriples = convertRef(r, referencedRows.get(fk), fk, baseURI);
            for (SemiStatement refTriple : refTriples) {
                tmpResult.add(refTriple);
            }
        }
        // Construct ref triples with correct subject
        for (SemiStatement refSemiTriple : tmpResult) {
            Statement triple = vf.createStatement(s, refSemiTriple.getPredicate(), refSemiTriple.getObject());
            if (triple != null)
                result.add(triple);
        }
        // Literal Triples
        // Difference with preceding Working Draft : even unary foreign key are
        // converted.
        for (String columnName : currentTable.getHeader().getColumnNames()) {
            Statement triple = convertLex(s, currentTable.getHeader(), r, columnName, baseURI);
            if (triple != null) {
                result.add(triple);
            }
        }
        // Update current table
        if (currentTableName != currentTable.getTableName()) {
            // currentTableName = currentTable.getTableName();
            Statement typeStatement = convertType(s, baseURI, currentTable);
            result.add(typeStatement);
        }
        return result;
    }

    /*
     * Convert type predicate from a row.
     */
    private Statement convertType(Resource s, String baseURI, StdTable currentTable) {
        // Table Triples
        URI typePredicate = vf.createURI(RDFPrefixes.prefix.get("rdf"), "type");
        URI typeObject = vf.createURI(baseURI, percentEncode(currentTable.getTableName(), false, false));
        Statement typeTriple = vf.createStatement(s, typePredicate, typeObject);
        return typeTriple;
    }

    /*
     * Denotational semantics function : convert lexical columns into a triple
     * with mapped (predicate, object).
     * 
     * @throws UnsupportedEncodingException
     */

    /**
     * Denotational semantics function : convert lexical columns into a triple
     * with mapped (predicate, object).
     * 
     * @param subject The subject to add for this triple.
     * @param header
     * @param r
     * @param columnName
     * @param baseURI
     * @return
     * @throws UnsupportedEncodingException
     */
    private Statement convertLex(Resource subject, StdHeader header, Row r, String columnName, String baseURI)
            throws UnsupportedEncodingException {

        log.debug("[DirectMappingEngine:convertLex] Table " + r.getParentBody().getParentTable().getTableName()
                + ", column : " + columnName);
        Statement result = null;
        Literal l = null;
        ArrayList<String> columnNames = new ArrayList<String>();
        columnNames.add(columnName);
        URI p = convertCol(r, columnNames, baseURI, false);
        byte[] v = r.getValues().get(columnName);
        String d = header.getDatatypes().get(columnName);
        if (v == null || v.equals("null")) {
            // Don't keep triple with null value
            return null;
        }
        DataType type = null;
        SQLType sqlType = SQLType.toSQLType(Integer.valueOf(d));
        if (sqlType.isBlobType()) {
            if (log.isDebugEnabled())
                log.debug("[DirectMappingEngine:convertLex] Table "
                        + r.getParentBody().getParentTable().getTableName() + ", column " + columnName
                        + " Forbidden BLOB type (binary stream not supported in XSD)"
                        + " => this triple will be ignored.");
            return null;
        } else {
            type = SQLToXMLS.getEquivalentType(Integer.valueOf(d));
            if (type == null)
                throw new IllegalStateException("[DirectMappingEngine:convertLex] Unknown XSD equivalent type of : "
                        + SQLType.toSQLType(Integer.valueOf(d)) + " in column : " + columnName + " in table : "
                        + r.getParentBody().getParentTable().getTableName());
        }
        // Canonical lexical form
        String v_str = XSDLexicalTransformation.extractNaturalRDFFormFrom(type, v);
        if (type.toString().equals(DataType.STRING.toString())) {
            l = vf.createLiteral(v_str);
        } else {
            URI datatype_iri = convertDatatype(d);
            if (datatype_iri == null) {
                l = vf.createLiteral(v_str);
            } else {
                l = vf.createLiteral(v_str, datatype_iri);
            }
        }
        result = vf.createStatement(subject, p, (Value) l);
        return result;
    }

    /*
     * Denotational semantics function : convert row and columnNames into
     * predicate URI.
     * 
     * @throws UnsupportedEncodingException
     */
    private URI convertCol(Row row, ArrayList<String> columnNames, String baseURI, boolean isRef)
            throws UnsupportedEncodingException {
        String label = percentEncode(row.getParentBody().getParentTable().getTableName(), false, false) + hash;
        if (isRef)
            label += refInfix;
        int i = 0;
        for (String columnName : columnNames) {
            i++;
            label += percentEncode(columnName, true, false);
            if (i < columnNames.size())
                label += semicolon;
        }
        // Check URI syntax
        if (!RDFDataValidator.isValidURI(baseURI + label)) {
            log.warn("[DirectMappingEngine:convertCol] This URI is not valid : " + baseURI + label);
            log.warn("[DirectMappingEngine:convertCol] This URI has been converted into : " + baseURI + label);
        }
        // Create value factory which build URI
        return vf.createURI(baseURI, label);
    }

    /*
     * Denotational semantics function : convert datatype from SQL into xsd
     * datatype.
     */
    private URI convertDatatype(String datatype) {
        String upDatatype = datatype.toUpperCase();
        if (!SQLToXMLS.isValidSQLDatatype(Integer.valueOf(upDatatype)))
            log.debug("[DirectMappingEngine:convertDatatype] Unknown datatype : " + datatype);
        String xsdDatatype = SQLToXMLS.getEquivalentType(Integer.valueOf(upDatatype)).toString();
        return vf.createURI(RDFPrefixes.prefix.get("xsd"), xsdDatatype);
    }

    /*
     * Objects accessors with adapted explicit conversion.
     */
    public String getReferencedTableName(Tuple tuple) {
        // Explicit conversion
        Row r = (Row) tuple;
        return r.getParentBody().getParentTable().getTableName();
    }

    public String getReferencedTableName(Key key) {
        // Explicit conversion
        ForeignKey referenceKey = (ForeignKey) key;
        return referenceKey.getTargetTableName();
    }

    public HashSet<Key> getReferencedKeys(Tuple tuple) {
        // Explicit conversion
        Row r = (Row) tuple;
        HashSet<Key> keys = new HashSet<Key>();
        for (ForeignKey fk : r.getParentBody().getParentTable().getForeignKeys())
            keys.add(fk);
        return keys;
    }

    public boolean isPrimaryKey(Key key, Tuple tuple) {
        // Explicit conversion
        ForeignKey fk = (ForeignKey) key;
        Row r = (Row) tuple;
        CandidateKey pk = r.getParentBody().getParentTable().getPrimaryKey();
        if (pk == null)
            return false;
        else
            return fk.matchSameColumns(pk);
    }
}