/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.rdfbean.rdb;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.sql.DataSource;
import org.openrdf.model.Statement;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandler;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.RDFParseException;
import org.openrdf.rio.RDFParser;
import org.openrdf.rio.Rio;
import org.openrdf.rio.helpers.RDFHandlerBase;
import com.google.common.base.Charsets;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.io.Resources;
import com.mysema.commons.lang.Assert;
import com.mysema.commons.lang.CloseableIterator;
import com.mysema.query.sql.SQLQuery;
import com.mysema.query.sql.SQLTemplates;
import com.mysema.rdfbean.CORE;
import com.mysema.rdfbean.Namespaces;
import com.mysema.rdfbean.TEST;
import com.mysema.rdfbean.model.Format;
import com.mysema.rdfbean.model.ID;
import com.mysema.rdfbean.model.IdSequence;
import com.mysema.rdfbean.model.LIT;
import com.mysema.rdfbean.model.NODE;
import com.mysema.rdfbean.model.Nodes;
import com.mysema.rdfbean.model.RDFBeanTransaction;
import com.mysema.rdfbean.model.RDFConnection;
import com.mysema.rdfbean.model.RDFConnectionCallback;
import com.mysema.rdfbean.model.Repository;
import com.mysema.rdfbean.model.RepositoryException;
import com.mysema.rdfbean.model.STMT;
import com.mysema.rdfbean.model.UID;
import com.mysema.rdfbean.model.XSD;
import com.mysema.rdfbean.model.io.RDFSource;
import com.mysema.rdfbean.model.io.RDFWriter;
import com.mysema.rdfbean.model.io.WriterUtils;
import com.mysema.rdfbean.object.Configuration;
import com.mysema.rdfbean.object.MappedClass;
import com.mysema.rdfbean.object.MappedPath;
import com.mysema.rdfbean.object.MappedPredicate;
import com.mysema.rdfbean.object.MappedProperty;
import com.mysema.rdfbean.rdb.support.SesameDialect;
import com.mysema.rdfbean.xsd.ConverterRegistry;
import com.mysema.rdfbean.xsd.ConverterRegistryImpl;
/**
* RDBRepository is a Repository implementation for the RDB module
*
* @author tiwe
* @version $Id$
*/
@Immutable
public class RDBRepository implements Repository {
private static RDFFormat getRioFormat(Format format) {
switch (format) {
case N3:
return RDFFormat.N3;
case NTRIPLES:
return RDFFormat.NTRIPLES;
case RDFA:
return RDFFormat.RDFA;
case RDFXML:
return RDFFormat.RDFXML;
case TRIG:
return RDFFormat.TRIG;
case TURTLE:
return RDFFormat.TURTLE;
}
throw new IllegalArgumentException("Unsupported format : " + format);
}
private static final int LOAD_BATCH_SIZE = 1000;
private final ConverterRegistry converterRegistry = new ConverterRegistryImpl();
private final IdFactory idFactory = new MD5IdFactory();
private final BiMap<NODE, Long> nodeCache = HashBiMap.create();
private final BiMap<Locale, Integer> langCache = HashBiMap.create();
private final Configuration configuration;
private final DataSource dataSource;
private final SQLTemplates templates;
private final IdSequence idSequence;
private final RDFSource[] sources;
public RDBRepository(
Configuration configuration,
DataSource dataSource,
SQLTemplates templates,
IdSequence idSequence,
RDFSource... sources) {
this.configuration = Assert.notNull(configuration, "configuration");
this.dataSource = Assert.notNull(dataSource, "dataSource");
this.templates = Assert.notNull(templates, "templates");
this.idSequence = Assert.notNull(idSequence, "idSequence");
this.sources = Assert.notNull(sources, "sources");
}
@Override
public void close() {
// do nothing
}
@Override
public <RT> RT execute(RDFConnectionCallback<RT> operation) {
RDFConnection connection = openConnection();
try {
try {
RDFBeanTransaction tx = connection.beginTransaction(false, 0,
Connection.TRANSACTION_READ_COMMITTED);
try {
RT retVal = operation.doInConnection(connection);
tx.commit();
return retVal;
} catch (IOException io) {
tx.rollback();
throw io;
}
} finally {
connection.close();
}
} catch (IOException io) {
throw new RepositoryException(io);
}
}
@Override
public void export(Format format, UID context, OutputStream out) {
export(format, Namespaces.DEFAULT, context, out);
}
@Override
public void export(Format format, Map<String, String> ns2prefix, UID context, OutputStream out) {
RDFWriter writer = WriterUtils.createWriter(format, out, ns2prefix);
RDFConnection conn = openConnection();
try {
CloseableIterator<STMT> stmts = conn.findStatements(null, null, null, context, false);
try {
writer.begin();
while (stmts.hasNext()) {
writer.handle(stmts.next());
}
writer.end();
} finally {
stmts.close();
}
} finally {
conn.close();
}
}
@Override
public void load(Format format, InputStream is, @Nullable UID context, boolean replace) {
ValueFactory valueFactory = new ValueFactoryImpl();
SesameDialect dialect = new SesameDialect(valueFactory);
RDBConnection connection = openConnection();
try {
if (!replace && context != null) {
if (!connection.find(null, null, null, context, false).isEmpty()) {
return;
}
}
if (context != null && replace) {
connection.deleteFromContext(context);
}
Set<STMT> stmts = new HashSet<STMT>(LOAD_BATCH_SIZE);
RDFParser parser = Rio.createParser(getRioFormat(format));
parser.setRDFHandler(createHandler(dialect, connection, stmts, context));
parser.parse(is, context != null ? context.getValue() : TEST.NS);
connection.update(Collections.<STMT> emptySet(), stmts);
} catch (RDFParseException e) {
throw new RepositoryException(e);
} catch (RDFHandlerException e) {
throw new RepositoryException(e);
} catch (IOException e) {
throw new RepositoryException(e);
} finally {
connection.close();
}
}
@Override
public void initialize() {
try {
initSchema();
initTables();
if (sources.length > 0) {
RDBConnection connection = openConnection();
try {
ValueFactory valueFactory = new ValueFactoryImpl();
SesameDialect dialect = new SesameDialect(valueFactory);
for (RDFSource source : sources) {
Set<STMT> stmts = new HashSet<STMT>(LOAD_BATCH_SIZE);
RDFFormat format = getRioFormat(source.getFormat());
RDFParser parser = Rio.createParser(format);
UID context = new UID(source.getContext());
connection.deleteFromContext(context);
parser.setRDFHandler(createHandler(dialect, connection, stmts, context));
parser.parse(source.openStream(), source.getContext());
connection.update(Collections.<STMT> emptySet(), stmts);
}
} catch (RDFParseException e) {
throw new RepositoryException(e);
} catch (RDFHandlerException e) {
throw new RepositoryException(e);
} finally {
connection.close();
}
}
} catch (IOException e) {
throw new RepositoryException(e);
} catch (SQLException e) {
throw new RepositoryException(e);
}
}
private RDFHandler createHandler(
final SesameDialect dialect,
final RDBConnection connection, final Set<STMT> stmts, @Nullable final UID context) {
return new RDFHandlerBase() {
@Override
public void handleStatement(Statement stmt) throws RDFHandlerException {
ID sub = dialect.getID(stmt.getSubject());
UID pre = dialect.getUID(stmt.getPredicate());
NODE obj = dialect.getNODE(stmt.getObject());
stmts.add(new STMT(sub, pre, obj, context));
if (stmts.size() == LOAD_BATCH_SIZE) {
connection.update(Collections.<STMT> emptySet(), stmts);
stmts.clear();
}
}
};
}
@SuppressWarnings("SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE")
private void initSchema() throws IOException, SQLException {
Connection conn = dataSource.getConnection();
try {
SQLQuery query = new SQLQuery(conn, templates).from(QLanguage.language);
query.count();
} catch (Exception e) {
java.sql.Statement stmt = conn.createStatement();
try {
URL res = Thread.currentThread().getContextClassLoader().getResource("h2.sql");
String sql = Resources.toString(res, Charsets.ISO_8859_1);
for (String clause : sql.split(";")) {
if (!clause.trim().isEmpty()) {
stmt.execute(clause.trim());
}
}
} finally {
stmt.close();
}
} finally {
conn.close();
}
}
private void initTables() throws IOException {
RDBConnection conn = openConnection();
try {
// init languages
Set<Locale> locales = new HashSet<Locale>(Arrays.asList(Locale.getAvailableLocales()));
locales.add(new Locale(""));
locales.add(new Locale("en"));
locales.add(new Locale("fi"));
locales.add(new Locale("sv"));
conn.addLocales(locales, langCache);
Set<NODE> nodes = new HashSet<NODE>();
// ontology resources
for (MappedClass mappedClass : configuration.getMappedClasses()) {
// class id
nodes.add(mappedClass.getUID());
// enum constants
if (mappedClass.isEnum()) {
for (Object e : mappedClass.getJavaClass().getEnumConstants()) {
nodes.add(new UID(mappedClass.getUID().ns(), ((Enum<?>) e).name()));
}
}
// property predicates
for (MappedPath path : mappedClass.getProperties()) {
MappedProperty<?> property = path.getMappedProperty();
if (property.getKeyPredicate() != null) {
nodes.add(property.getKeyPredicate());
}
if (property.getValuePredicate() != null) {
nodes.add(property.getValuePredicate());
}
for (MappedPredicate predicate : path.getPredicatePath()) {
nodes.add(predicate.getUID());
}
}
}
// common resources
nodes.add(new UID("default:default"));
nodes.add(CORE.localId);
nodes.add(RDB.nullContext);
nodes.addAll(Nodes.all);
// common literals
nodes.add(new LIT(""));
nodes.add(new LIT("true", XSD.booleanType));
nodes.add(new LIT("false", XSD.booleanType));
// dates
nodes.add(new LIT(converterRegistry.toString(new java.sql.Date(0)), XSD.date));
nodes.add(new LIT(converterRegistry.toString(new java.util.Date(0)), XSD.dateTime));
// letters
for (char c = 'a'; c <= 'z'; c++) {
String str = String.valueOf(c);
nodes.add(new LIT(str));
nodes.add(new LIT(str.toUpperCase(Locale.ENGLISH)));
}
// numbers
for (int i = -128; i < 128; i++) {
String str = String.valueOf(i);
nodes.add(new LIT(str));
nodes.add(new LIT(str, XSD.byteType));
nodes.add(new LIT(str, XSD.shortType));
nodes.add(new LIT(str, XSD.intType));
nodes.add(new LIT(str, XSD.longType));
nodes.add(new LIT(str, XSD.integerType));
nodes.add(new LIT(str + ".0", XSD.floatType));
nodes.add(new LIT(str + ".0", XSD.doubleType));
nodes.add(new LIT(str + ".0", XSD.decimalType));
}
conn.addNodes(nodes, nodeCache);
} finally {
conn.close();
}
}
@Override
public RDBConnection openConnection() {
try {
Connection connection = dataSource.getConnection();
RDBContext context = new RDBContext(
converterRegistry,
idFactory,
nodeCache, langCache,
idSequence,
connection,
templates);
return new RDBConnection(context);
} catch (SQLException e) {
throw new RepositoryException(e);
}
}
}