SemtoolStructureManagerImpl.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ostrichemulators.semtool.rdf.engine.util;

import com.ostrichemulators.semtool.model.vocabulary.SEMTOOL;
import com.ostrichemulators.semtool.rdf.engine.api.IEngine;
import com.ostrichemulators.semtool.rdf.engine.api.ModificationExecutor;
import com.ostrichemulators.semtool.rdf.query.util.ModificationExecutorAdapter;
import com.ostrichemulators.semtool.rdf.query.util.impl.ListQueryAdapter;
import com.ostrichemulators.semtool.rdf.query.util.impl.ModelQueryAdapter;
import com.ostrichemulators.semtool.rdf.query.util.impl.OneValueQueryAdapter;
import com.ostrichemulators.semtool.rdf.query.util.impl.OneVarListQueryAdapter;
import com.ostrichemulators.semtool.rdf.query.util.impl.VoidQueryAdapter;
import com.ostrichemulators.semtool.util.Constants;
import com.ostrichemulators.semtool.util.UriBuilder;
import com.ostrichemulators.semtool.util.Utility;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.openrdf.model.Model;
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.LinkedHashModel;
import org.openrdf.model.vocabulary.OWL;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.query.BindingSet;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;

/**
 * An implementation that queries the IEngine for {@link SEMTOOL#Structure}
 * triples. This class is *not* thread-safe, but it is very lightweight. Note
 * that no matter what is passed into these functions, top-level results are
 * returned...so, for example, if the Person concept has three possible
 * properties, but an instance of Person is passed into getPropertiesOf, three
 * properties will be returned, even if that particular instance doesn't have
 * any of them set.
 *
 * @author ryan
 */
public final class SemtoolStructureManagerImpl implements StructureManager {

	private static final Logger log = Logger.getLogger( SemtoolStructureManagerImpl.class );

	private final IEngine engine;
	private final ListQueryAdapter<URI> propqa = OneVarListQueryAdapter.getUriList(
			"SELECT ?prop WHERE {\n"
			+ "  ?s a semtool:StructureData ;"
			+ "    rdfs:domain ?domain ;"
			+ "    owl:DatatypeProperty ?prop ."
			+ "    FILTER NOT EXISTS { ?s rdf:predicate ?x  }\n"
			+ "  ?dom a|rdfs:subPropertyOf* ?domain .\n"
			+ "}" );

	protected SemtoolStructureManagerImpl( IEngine engine ) {
		this.engine = engine;
	}

	@Override
	public Set<URI> getPropertiesOf( URI type ) {
		propqa.bind( "dom", type );
		Set<URI> set = new HashSet<>( engine.queryNoEx( propqa ) );
		if ( set.isEmpty() ) {
			// check to see if type is actually a relation
			set.addAll( getPropertiesOf( null, type, null ) );
		}

		return set;
	}

	@Override
	public Set<URI> getPropertiesOf( URI subtype, URI predtype, URI objtype ) {
		Map<String, Value> bindings = new HashMap<>();
		bindings.put( "pred", predtype );

		String query = "SELECT ?prop WHERE {\n"
				+ "  ?s a semtool:StructureData ;"
				+ "     rdfs:domain ?domain ;"
				+ "     rdfs:range ?range ;"
				+ "     rdf:predicate ?predicate ;"
				+ "     owl:DatatypeProperty ?prop .\n"
				+ "  ?pred a|rdfs:subPropertyOf* ?predicate .\n";
		if ( !( null == subtype || Constants.ANYNODE == subtype ) ) {
			query += "  ?dom a|rdfs:subClassOf+ ?domain .\n";
			bindings.put( "dom", subtype );
		}
		if ( !( null == objtype || Constants.ANYNODE == objtype ) ) {
			query += "  ?ran a|rdfs:subClassOf+ ?range .\n";
			bindings.put( "ran", objtype );
		}
		query += "}";

		OneVarListQueryAdapter<URI> qa = OneVarListQueryAdapter.getUriList( query );
		qa.setBindings( bindings );
		return new HashSet<>( engine.queryNoEx( qa ) );

	}

	@Override
	public Model getLinksBetween( URI subtype, URI objtype ) {
		String query = "CONSTRUCT{ ?s ?p ?o } WHERE { \n"
				+ "  ?mm a semtool:StructureData ;\n"
				+ "    rdf:predicate ?p ;\n"
				+ "    rdfs:domain ?s ;\n"
				+ "    rdfs:range ?o .\n";
		// + "}"

		Map<String, Value> bindings = new HashMap<>();
		if ( !Constants.ANYNODE.equals( subtype ) ) {
			query += "   ?dom a|rdfs:subClassOf* ?s .\n";
			bindings.put( "dom", subtype );
		}
		if ( !Constants.ANYNODE.equals( objtype ) ) {
			query += "   ?ran a|rdfs:subClassOf* ?o .\n";
			bindings.put( "ran", objtype );
		}
		query += "}";

		ModelQueryAdapter modelqa = new ModelQueryAdapter( query );
		modelqa.setBindings( bindings );

		return engine.constructNoEx( modelqa );
	}

	@Override
	public Model getEndpoints( URI reltype ) {
		String query = "CONSTRUCT{ ?s ?p ?o } WHERE { \n"
				+ "  ?mm a semtool:StructureData ;\n"
				+ "    rdf:predicate ?p ;\n"
				+ "    rdfs:domain ?s ;\n"
				+ "    rdfs:range ?o .\n"
				+ "  ?rel a|rdfs:subPropertyOf* ?p .\n"
				+ "}";

		ModelQueryAdapter modelqa = new ModelQueryAdapter( query );
		modelqa.bind( "rel", reltype );

		return engine.constructNoEx( modelqa );
	}

	@Override
	public Set<URI> getTopLevelRelations( Collection<URI> instances ) {
		if ( null == instances ) {
			ListQueryAdapter<URI> lqa = OneVarListQueryAdapter.getUriList(
					"SELECT ?p WHERE { ?mm a semtool:StructureData ; rdf:predicate ?p . }" );
			return new HashSet<>( engine.queryNoEx( lqa ) );
		}

		if ( instances.isEmpty() ) {
			return new HashSet<>();
		}

		String implosion = Utility.implode( instances );
		ListQueryAdapter<URI> lqa = OneVarListQueryAdapter.getUriList(
				"SELECT ?p WHERE { ?mm a semtool:StructureData ; rdf:predicate ?p . "
				+ "?reltype a|rdfs:subPropertyOf* ?p . "
				+ "VALUES ?reltype {" + implosion + "} }" );
		return new HashSet<>( engine.queryNoEx( lqa ) );
	}

	@Override
	public Set<URI> getTopLevelConcepts() {
		ListQueryAdapter<URI> lqa = OneVarListQueryAdapter.getUriList(
				"SELECT ?s WHERE { ?s rdfs:subClassOf ?concept }" );
		lqa.bind( "concept", engine.getSchemaBuilder().getConceptUri().build() );
		lqa.useInferred( false );
		return new HashSet<>( engine.queryNoEx( lqa ) );
	}

	@Override
	public Model getConnectedConceptTypes( Collection<URI> instances ) {
		final String query = "CONSTRUCT{ ?s ?p ?o } WHERE { \n"
				+ "  ?mm a semtool:StructureData ;\n"
				+ "    rdf:predicate ?p ;\n"
				+ "    rdfs:domain ?s ;\n"
				+ "    rdfs:range ?o .\n";
		String implosion = Utility.implode( instances );

		LinkedHashModel model = new LinkedHashModel();
		ModelQueryAdapter subjects = new ModelQueryAdapter( query
				+ " ?dom a|rdfs:subClassOf* ?s . VALUES ?dom {" + implosion + "} }", model );
		ModelQueryAdapter objects = new ModelQueryAdapter( query
				+ " ?ran a|rdfs:subClassOf* ?o . VALUES ?ran {" + implosion + "} }", model );
		engine.constructNoEx( subjects );
		engine.constructNoEx( objects );

		return model;
	}

	@Override
	public Model getModel() {
		ModelQueryAdapter qa = new ModelQueryAdapter(
				"CONSTRUCT { ?s ?p ?o } WHERE { ?s a ?struct ; ?p ?o .}" );
		qa.bind( "struct", SEMTOOL.Structure );
		qa.useInferred( false );
		return engine.constructNoEx( qa );
	}

	private Model doRebuild( Collection<URI> uris ) {
		// get all concepts
		String cquery = "SELECT DISTINCT ?instance WHERE { ?instance rdfs:subClassOf ?concept }";
		ListQueryAdapter<URI> cqa = OneVarListQueryAdapter.getUriList( cquery );
		cqa.bind( "concept", engine.getSchemaBuilder().getConceptUri().build() );
		cqa.useInferred( false );
		List<URI> concepts = engine.queryNoEx( cqa );
		if ( null != uris ) {
			concepts.retainAll( uris );
		}

		// get all edge types
		String equery = "SELECT DISTINCT ?instance WHERE { ?instance rdfs:subPropertyOf ?semrel }";
		ListQueryAdapter<URI> eqa = OneVarListQueryAdapter.getUriList( equery );
		eqa.bind( "semrel", engine.getSchemaBuilder().getRelationUri().build() );
		eqa.useInferred( false );
		List<URI> edges = engine.queryNoEx( eqa );
		if ( null != uris ) {
			edges.retainAll( uris );
		}

		Model edgemodel = rebuildEdges( concepts, edges );
		Model propmodel = rebuildConceptProps( concepts );
		Set<Resource> needlabels = new HashSet<>();

		for ( Statement s : edgemodel ) {
			needlabels.add( URI.class.cast( s.getSubject() ) );
			needlabels.add( s.getPredicate() );
			needlabels.add( URI.class.cast( s.getObject() ) );
		}
		for ( Statement s : propmodel ) {
			needlabels.add( URI.class.cast( s.getSubject() ) );
			needlabels.add( s.getPredicate() );
		}

		Map<Resource, String> labels = Utility.getInstanceLabels( needlabels, engine );
		Map<String, URI> structurelkp = new HashMap<>();
		UriBuilder schema = engine.getSchemaBuilder();

		LinkedHashModel model = new LinkedHashModel();

		for ( Statement s : edgemodel ) {
			String sub = labels.get( s.getSubject() );
			String rel = labels.get( s.getPredicate() );
			String obj = labels.get( Resource.class.cast( s.getObject() ) );
			String name = sub + "_" + rel + "_" + obj;

			model.addAll( getEdgeStructure( s.getPredicate(),
					URI.class.cast( s.getSubject() ), URI.class.cast( s.getObject() ),
					schema, structurelkp, name ) );
		}

		model.addAll( createEdgeProps( concepts, edges, structurelkp, schema, labels ) );

		for ( Statement s : propmodel ) {
			String sub = labels.get( s.getSubject() );
			String rel = labels.get( s.getPredicate() );
			String name = sub + "_" + rel;

			model.addAll( getPropStructure( s.getPredicate(),
					URI.class.cast( s.getSubject() ), schema, structurelkp, name ) );
		}

		return model;
	}

	@Override
	public Model rebuild( Collection<URI> uris ) {
		return doRebuild( uris );
	}

	@Override
	public Model rebuild( boolean saveToEngine ) {
		Model model = doRebuild( null );

		if ( saveToEngine ) {
			save( model );
		}

		return model;
	}

	private Model rebuildConceptProps( List<URI> concepts ) {
		String cimplosion = Utility.implode( concepts );

		// now see what properties are on concepts and edges
		String query = "SELECT DISTINCT ?type ?prop WHERE {\n"
				+ "  ?s ?prop ?propval . FILTER ( isLiteral( ?propval ) )\n"
				+ "  ?s a|rdfs:subClassOf+ ?type .\n"
				+ "  FILTER ( ?prop != rdfs:label ) .\n"
				+ "} VALUES ?type { " + cimplosion + " }";

		ModelQueryAdapter mqa = new ModelQueryAdapter( query ) {

			@Override
			public void handleTuple( BindingSet set, ValueFactory fac ) {
				URI type = URI.class.cast( set.getValue( "type" ) );
				URI prop = URI.class.cast( set.getValue( "prop" ) );
				result.add( type, prop, Constants.ANYNODE );
			}
		};

		return engine.queryNoEx( mqa );
	}

	private Model rebuildEdges( List<URI> concepts, List<URI> edges ) {
		String cimplosion = Utility.implode( concepts );
		String eimplosion = Utility.implode( edges );

		// now see what concepts connect to others via our edge types
		String query = "CONSTRUCT { ?stype ?rtype ?otype } WHERE {\n"
				+ "  ?s a|rdfs:subClassOf+ ?stype .\n"
				+ "  ?o a|rdfs:subClassOf+ ?otype .\n"
				+ "  ?r a|rdfs:subPropertyOf+ ?rtype .\n"
				+ "  ?s ?r ?o .\n"
				+ "  VALUES ?stype {" + cimplosion + "} .\n"
				+ "  VALUES ?otype {" + cimplosion + "} .\n"
				+ "  VALUES ?rtype {" + eimplosion + "} .\n"
				+ "}";

		final Model model = engine.constructNoEx( new ModelQueryAdapter( query ) );
		return model;
	}

	private Model createEdgeProps( List<URI> concepts, List<URI> edges,
			Map<String, URI> structurelkp, UriBuilder schema,
			Map<Resource, String> labels ) {
		String cimplosion = Utility.implode( concepts );
		String eimplosion = Utility.implode( edges );

		String propq = "SELECT DISTINCT ?stype ?rtype ?otype ?prop WHERE {\n"
				+ "  ?s a|rdfs:subClassOf+ ?stype .\n"
				+ "  ?o a|rdfs:subClassOf+ ?otype .\n"
				+ "  ?r a|rdfs:subPropertyOf+ ?rtype .\n"
				+ "  ?s ?r ?o .\n"
				+ "  ?r ?prop ?propval . FILTER( ?prop != rdfs:label && isLiteral( ?propval ) ) .\n"
				+ "  VALUES ?stype {" + cimplosion + "} .\n"
				+ "  VALUES ?otype {" + cimplosion + "} .\n"
				+ "  VALUES ?rtype {" + eimplosion + "} .\n"
				+ "}";

		Model model = new LinkedHashModel();

		VoidQueryAdapter vqa = new VoidQueryAdapter( propq ) {
			@Override
			public void handleTuple( BindingSet set, ValueFactory fac ) {
				URI domain = URI.class.cast( set.getValue( "stype" ) );
				URI pred = URI.class.cast( set.getValue( "rtype" ) );
				URI range = URI.class.cast( set.getValue( "otype" ) );
				URI prop = URI.class.cast( set.getValue( "prop" ) );

				String sub = labels.get( domain );
				String rel = labels.get( pred );
				String obj = labels.get( range );
				String name = sub + "_" + rel + "_" + obj;

				model.addAll( getPropStructure( pred, domain, range, prop, schema,
						structurelkp, name ) );

			}
		};

		engine.queryNoEx( vqa );

		return model;
	}

	private void save( Model model ) {
		// get old model, which we'll remove
		Model olds = getModel();

		ModificationExecutor eme = new ModificationExecutorAdapter( true ) {

			@Override
			public void exec( RepositoryConnection conn ) throws RepositoryException {
				conn.remove( olds );
				conn.add( model );
			}
		};

		try {
			engine.execute( eme );
		}
		catch ( Exception e ) {
			log.error( e, e );
		}
	}

	public static Collection<Statement> getEdgeStructure( URI predicate, URI domain,
			URI range, UriBuilder schema, Map<String, URI> structurelkp, String name ) {

		Model stmts = new LinkedHashModel();

		String key = predicate.stringValue() + domain + range;
		if ( !structurelkp.containsKey( key ) ) {
			URI structure = schema.build( name );
			structurelkp.put( key, structure );
		}

		URI structure = structurelkp.get( key );

		stmts.add( structure, RDF.TYPE, SEMTOOL.Structure );
		stmts.add( structure, RDF.PREDICATE, predicate );
		stmts.add( structure, RDFS.DOMAIN, domain );
		stmts.add( structure, RDFS.RANGE, range );

		return stmts;
	}

	public static Collection<Statement> getPropStructure( URI prop, URI domain,
			UriBuilder schema, Map<String, URI> structurelkp, String title ) {

		Model stmts = new LinkedHashModel();

		String key = prop.stringValue() + domain;
		if ( !structurelkp.containsKey( key ) ) {
			URI structure = schema.build( title );
			structurelkp.put( key, structure );
		}

		URI structure = structurelkp.get( key );

		stmts.add( structure, RDF.TYPE, SEMTOOL.Structure );
		stmts.add( structure, OWL.DATATYPEPROPERTY, prop );
		stmts.add( structure, RDFS.DOMAIN, domain );

		return stmts;
	}

	public static Collection<Statement> getPropStructure( URI predicate, URI domain,
			URI range, URI prop, UriBuilder schema, Map<String, URI> structurelkp,
			String title ) {

		Model stmts = new LinkedHashModel();

		String key = prop.stringValue() + domain;
		if ( !structurelkp.containsKey( key ) ) {
			URI structure = schema.build( title );
			structurelkp.put( key, structure );
		}

		URI structure = structurelkp.get( key );

		stmts.add( structure, RDF.TYPE, SEMTOOL.Structure );
		stmts.add( structure, RDF.PREDICATE, predicate );
		stmts.add( structure, RDFS.DOMAIN, domain );
		stmts.add( structure, RDFS.RANGE, range );
		stmts.add( structure, OWL.DATATYPEPROPERTY, prop );

		return stmts;
	}

	@Override
	public URI getTopLevelType( URI instance ) {
		String query = "SELECT ?type WHERE { "
				+ "  ?subject a|rdfs:subClassOf|rdfs:subPropertyOf ?type "
				+ "}";
		OneValueQueryAdapter<URI> qa = OneValueQueryAdapter.getUri( query );
		qa.bind( "subject", instance );
		qa.useInferred( false );
		String q = qa.bindAndGetSparql();
		log.debug( "type query: " + q );

		return engine.queryNoEx( qa );
	}
}