/*
* (c) Copyright 2006 Hewlett-Packard Development Company, LP
* All rights reserved.
* [See end of file]
*/
package com.hp.hpl.squirrelrdf.rdb;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.squirrelrdf.querymap.exceptions.ConfigException;
import com.hp.hpl.squirrelrdf.querymap.exceptions.InconsistentException;
import com.hp.hpl.squirrelrdf.querymap.exceptions.UnanswerableException;
/**
*
* RDF2SQLConfig
*
* Represents a map from RDF (properties, classes, resources) to
* SQL (tables, columns, rows).
*
* @author Damian Steer <pldms@mac.com>
*
*/
public class RDF2SQLConfig
{
final static Log log = LogFactory.getLog(RDF2SQLConfig.class);
private Model configModel;
private Resource map;
private Map<Resource,Col> prop2col;
private Map<Resource,Database> res2db;
private Map<Resource,Table> class2table;
/**
* Initialise an RDF2SQL map from a configuration model.
*
* @param configModel The RDF to SQL map configuration.
* @throws ConfigException If the configuration doesn't work.
*/
public RDF2SQLConfig(Model configModel) throws ConfigException
{
this.configModel = configModel;
this.prop2col = new HashMap<Resource,Col>();
this.res2db = new HashMap<Resource,Database>();
this.class2table = new HashMap<Resource,Table>();
initFromConfigModel();
}
/**
* Initialise from config model. Generates the Col, Table, Database representation.
* @throws ConfigException
*/
private void initFromConfigModel() throws ConfigException
{
ResIterator ri = configModel.listSubjectsWithProperty(RDF.type, DbMap.Map);
if (!ri.hasNext())
throw new ConfigException("No map in config file");
this.map = ri.nextResource();
if (ri.hasNext())
log.warn("There is more than one map in the config file");
StmtIterator si = map.listProperties(DbMap.mapsClass);
while (si.hasNext())
{
Resource aClass = si.nextStatement().getResource();
initFromClass(aClass);
}
si.close();
si = configModel.listStatements(null, DbMap.foreignKey, (RDFNode) null);
while (si.hasNext())
{
Statement s = si.nextStatement();
Col subj = prop2col.get(s.getSubject());
Col obj = prop2col.get(s.getObject());
if (subj == null || obj == null)
log.warn("Foreign key missed: " + s);
subj.setForeignKey(obj);
}
}
/**
* Create a table and cols for an rdf class
* @param aClass
* @throws ConfigException
*/
private void initFromClass(Resource aClass) throws ConfigException
{
if (class2table.containsKey(aClass)) return; // already done
Resource db = aClass.getProperty(DbMap.database).getResource();
Database database = getDatabase(db);
Table table = new Table(aClass, database);
class2table.put(aClass, table);
ResIterator ri = aClass.getModel().listSubjectsWithProperty(RDFS.domain, aClass);
while (ri.hasNext())
{
Resource prop = ri.nextResource();
Col col = new Col(prop, table);
prop2col.put(prop, col);
}
StmtIterator si = aClass.listProperties(DbMap.primaryKey);
while (si.hasNext())
{
Resource primKey = si.nextStatement().getResource();
Col primCol = prop2col.get(primKey);
if (primCol == null)
throw new ConfigException("Primary key unknown: " + primKey);
else
primCol.setIsPrimaryKey(true);
}
}
/**
* Create a database from a config resource
* @param db
* @return
*/
private Database getDatabase(Resource db)
{
if (res2db.containsKey(db))
return res2db.get(db);
Literal user = db.getProperty(DbMap.user).getLiteral();
Literal pass = db.getProperty(DbMap.pass).getLiteral();
Literal driver = db.getProperty(DbMap.driver).getLiteral();
Database database = new Database(db, user, pass, driver);
res2db.put(db, database); // don't keep creating !!
return database;
}
/**
* Preprocess a query pattern.
*
* We can eliminate quite a few queries at this point, simply because
* they can't be answered.
*
* @param triples A query pattern
* @return A query, ready to execute.
* @throws InconsistentException If the query can't be answered by this map, ever.
* @throws UnanswerableException If the query can't be answered due to some limitation of the map.
*/
public ProcessedQuery createQuery(List<Triple> triples) throws InconsistentException, UnanswerableException
{
ProcessedQuery query = preprocessTriples(triples);
return query;
}
private ProcessedQuery preprocessTriples(List<Triple> triples) throws InconsistentException, UnanswerableException
{
ProcessedQuery query = new ProcessedQuery();
Map<Node,Resource> nodeToClass = new HashMap<Node,Resource>(); // for sanity checking
for (Triple triple: triples)
{
Node subject = triple.getSubject();
Node predicate = triple.getPredicate();
Node object = triple.getObject();
if (predicate.isVariable())
throw new UnanswerableException("I can't do predicate matches (yet).");
if (predicate.equals(RDF.Nodes.type)) // RDB map is staticly typed, so this is not that useful
{
if (object.isConcrete())
{
Resource theClass = (Resource) configModel.asRDFNode(object);
Table table = class2table.get(theClass);
if (table == null)
throw new InconsistentException("Unknown class: " + object);
checkClasses(subject, theClass, nodeToClass);
nodeToClass.put(subject, theClass);
query.add(subject, table); // Just add the subject and table to the query.
}
else
throw new UnanswerableException("No type matches (yet)");
continue;
}
Resource prop = (Resource) configModel.getRDFNode(predicate);
if (!prop2col.containsKey(prop))
throw new InconsistentException("Unknown predicate: " + triple);
/* Domain / Range checks -- subjects have types specific to tables, objects are literals */
checkClasses(subject, prop.getProperty(RDFS.domain).getResource(), nodeToClass);
checkClasses(object, RDFS.Literal, nodeToClass);
//if (subject.isConcrete())
// throw new UnanswerableException("Concrete subject given: " + triple);
if (object.isConcrete() && !object.isLiteral())
throw new UnanswerableException("Object is resource: " + triple);
// Ok, that's all the problem cases out the way (well, mostly)
Col col = prop2col.get(prop);
// Cleaning: bnodes -> =var or #var. That will hurt.
subject = clean(subject);
object = clean(object);
query.add(subject, col, object);
}
return query;
}
/*
* Clean nodes. Bnodes in sparql become (rather funky) variables, whose names need fixing
*/
private Node clean(Node node)
{
if (!node.isVariable()) return node;
if (node.getName().startsWith("="))
return Node.createVariable(node.getName().replaceFirst("=","bnode\\$"));
if (node.getName().startsWith("#"))
return Node.createVariable(node.getName().replaceFirst("#","bnode\\$"));
return node;
}
/* domain / range checking */
private void checkClasses(Node node, Resource aClass, Map<Node,Resource> nodeToClass)
throws InconsistentException
{
if (!nodeToClass.containsKey(node))
{
nodeToClass.put(node, aClass);
return;
}
Resource existingClass = (Resource) nodeToClass.get(node);
if (!existingClass.equals(aClass))
throw new InconsistentException("Inconsistent classes for " + node +
": both " + existingClass + " and " + aClass);
}
/**
* Close all existing database connections
*
* @throws SQLException
*/
public void close() throws SQLException
{
for (Database db: res2db.values())
{
db.closeConnection();
}
}
}
/*
* (c) Copyright 2006 Hewlett-Packard Development Company, LP
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/