/**
*
*/
package org.ihtsdo.otf.snomed.service;
import info.aduna.iteration.CloseableIteration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.configuration.Configuration;
import org.ihtsdo.otf.refset.exception.EntityNotFoundException;
import org.ihtsdo.otf.snomed.domain.Concept;
import org.ihtsdo.otf.snomed.exception.ConceptServiceException;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.Binding;
import org.openrdf.query.BindingSet;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.impl.EmptyBindingSet;
import org.openrdf.query.impl.MapBindingSet;
import org.openrdf.query.parser.ParsedQuery;
import org.openrdf.query.parser.sparql.SPARQLParser;
import org.openrdf.sail.Sail;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import com.thinkaurelius.titan.core.TitanFactory;
import com.thinkaurelius.titan.core.TitanGraph;
import com.tinkerpop.blueprints.oupls.sail.GraphSail;
/**
* @author Episteme Partners
*
*/
public class ConceptLookUpServiceImpl implements ConceptLookupService {
private static final Logger LOGGER = LoggerFactory.getLogger(ConceptLookUpServiceImpl.class);
private static final String BASE_URI = "http://sct.snomed.info/";
private TitanGraph graph;
private static final String Q_CONCEPT_ID = "prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
+ "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
+ "prefix owl: <http://www.w3.org/2002/07/owl#>"
+ "prefix xsd: <http://www.w3.org/2001/XMLSchema#>"
+ "prefix sn: <http://sct.snomed.info/#>"
+ "SELECT DISTINCT ?x "
+ " WHERE { "
+ "?x ?o ?y. \n"
+ "?x sn:description ?desc. \n"
+ "?desc ?do ?dy \n"
+ "} limit %d \n OFFSET %d ";
private static final String Q_CONCEPT = "prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
+ "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
+ "prefix owl: <http://www.w3.org/2002/07/owl#>"
+ "prefix xsd: <http://www.w3.org/2001/XMLSchema#>"
+ "prefix sn: <http://sct.snomed.info/#>"
+ "SELECT ?x ?o ?y "
+ " WHERE { "
+ "?x ?o ?y. \n"
+ "?x sn:description ?desc.\n"
+ "?desc ?do ?dy \n"
+ "}";
private static final String Q_TYPES = "prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n"
+ "prefix sn: <http://sct.snomed.info/#>\n"
+"select ?SubTypeName ?SubTypeId ?id where {"
+"?SubTypeId rdfs:subClassOf ?id.\n"
+"?SubTypeId rdfs:label ?SubTypeName.\n"
+"} limit 100";
private String repositoryConfig;
private Configuration config;
/* (non-Javadoc)
* @see org.ihtsdo.otf.snomed.service.ConceptLookupService#getConcepts(java.util.List)
*/
@Override
public Map<String, Concept> getConcepts(Set<String> conceptIds)
throws ConceptServiceException {
LOGGER.debug("getting concepts details for {}", conceptIds);
Map<String, Concept> concepts = new HashMap<String, Concept>();
TitanGraph g = null;
Sail sail = null;
SailConnection sc = null;
try {
g = getGraph();
sail = getSail(g);
sail.initialize();
sc = sail.getConnection();
SPARQLParser parser = new SPARQLParser();
CloseableIteration<? extends BindingSet, QueryEvaluationException> sparqlResults;
ParsedQuery sparql = parser.parseQuery(Q_CONCEPT, "http://sct.snomed.info");
ValueFactory vf = sail.getValueFactory();
for (String id : conceptIds) {
if (StringUtils.isEmpty(id)) {
//ignore all null keys or empty keys
LOGGER.debug("ingoring {} for concepts details as invalid", id);
continue;
}
URI uri = vf.createURI(BASE_URI + id);
MapBindingSet bindings = new MapBindingSet();
bindings.addBinding("x", uri);
sparqlResults = sc.evaluate(sparql.getTupleExpr(), sparql.getDataset(), bindings, true);
Concept c = getConcept(sparqlResults);
concepts.put(id, c);
}
} catch (Exception e) {
LOGGER.error("Error duing concept details for concept map fetch", e);
throw new ConceptServiceException(e);
} finally {
close(sc);
close(sail);
close(g);
}
LOGGER.debug("returning total {} concepts ", concepts.size());
return Collections.unmodifiableMap(concepts);
}
/* (non-Javadoc)
* @see org.ihtsdo.otf.snomed.service.ConceptLookupService#getConcept(java.lang.String)
*/
@Override
public Concept getConcept(String conceptId) throws ConceptServiceException,
EntityNotFoundException {
LOGGER.debug("getting concept details for {} ", conceptId);
if (StringUtils.isEmpty(conceptId)) {
throw new EntityNotFoundException(String.format("Invalid concept id", conceptId));
}
TitanGraph g = null;
Sail sail = null;
SailConnection sc = null;
try {
g = getGraph();
sail = getSail(g);
sail.initialize();
sc = sail.getConnection();
SPARQLParser parser = new SPARQLParser();
CloseableIteration<? extends BindingSet, QueryEvaluationException> sparqlResults;
ParsedQuery sparql = parser.parseQuery(Q_CONCEPT, "http://sct.snomed.info");
ValueFactory vf = sail.getValueFactory();
URI uri = vf.createURI(BASE_URI + conceptId);
MapBindingSet bindings = new MapBindingSet();
bindings.addBinding("x", uri);
sparqlResults = sc.evaluate(sparql.getTupleExpr(), sparql.getDataset(), bindings, true);
return getConcept(sparqlResults);
} catch (Exception e) {
LOGGER.error("Error duing concept details fetch", e);
throw new ConceptServiceException(e);
} finally {
close(sc);
close(sail);
close(g);
}
}
/* (non-Javadoc)
* @see org.ihtsdo.otf.snomed.service.ConceptLookupService#getConceptIds(int, int)
*/
@Override
public Set<String> getConceptIds(int offset, int limit)
throws ConceptServiceException {
LOGGER.debug("getting concept ids with offset {} and limit {} ", offset, limit);
TreeSet<String> conceptIds = new TreeSet<String>();
TitanGraph g = null;
Sail sail = null;
SailConnection sc = null;
try {
g = getGraph();
sail = getSail(g);
sail.initialize();
sc = sail.getConnection();
SPARQLParser parser = new SPARQLParser();
CloseableIteration<? extends BindingSet, QueryEvaluationException> sparqlResults;
limit = limit <= 0 ? 4000 : limit;
offset = limit <= 0 ? 1 : offset;
String query = String.format(Q_CONCEPT_ID, limit, offset);
ParsedQuery sparql = parser.parseQuery(query, "http://sct.snomed.info");
sparqlResults = sc.evaluate(sparql.getTupleExpr(), sparql.getDataset(), new EmptyBindingSet(), true);
while (sparqlResults.hasNext()) {
BindingSet binding = sparqlResults.next();
if (binding.hasBinding("x")) {
Value v = binding.getValue("x");
if (v != null && v.stringValue().contains(BASE_URI)) {
String conceptId = StringUtils.delete(v.stringValue(), BASE_URI);
LOGGER.debug("Adding concept ids {} to list", conceptId);
conceptIds.add(conceptId);
}
} else {
LOGGER.debug("Binding not available"); //TODO remove later
}
}
sparqlResults.close();
} catch (Exception e) {
LOGGER.error("Error duing concept ids fetch ", e);
throw new ConceptServiceException(e);
} finally {
close(sc);
close(sail);
close(g);
}
LOGGER.debug("returning total {} concept ids ", conceptIds.size());
return Collections.unmodifiableSortedSet(conceptIds);
}
/**
* @param sail
*/
private void close(Sail sail) {
if (sail != null) {
LOGGER.debug("Shutting down sail storage");
try {
sail.shutDown();
} catch (SailException e) {
// TODO Auto-generated catch block
LOGGER.error("Error in sail shutdown", e);
}
}
}
/**
* @param sc
*/
private void close(SailConnection sc) {
// TODO Auto-generated method stub
if (sc != null) {
LOGGER.debug("Shutting down sc storage");
try {
sc.close();
} catch (SailException e) {
// TODO Auto-generated catch block
LOGGER.error("Error in saill connection closing", e);
}
}
}
/**
* @param g
*/
private void close(TitanGraph g) {
if (g != null) {
LOGGER.debug("Shutting down graph storage");
g.shutdown();
}
}
/**
* @param repositoryConfig the repositoryConfig to set
*/
public void setRepositoryConfig(String repositoryConfig) {
this.repositoryConfig = repositoryConfig;
}
private Sail getSail(TitanGraph graph) {
GraphSail<TitanGraph> sail = new GraphSail<TitanGraph>(graph);
sail.enforceUniqueStatements(true);
return sail;
}
private TitanGraph getGraph() {
TitanGraph g = this.graph;
if ( g != null && g.isOpen() ) {
return g;
}
if( config != null) {
this.graph = TitanFactory.open(config);
return this.graph;
}
if (!StringUtils.isEmpty(repositoryConfig)) {
this.graph = TitanFactory.open(repositoryConfig);
return this.graph;
}
throw new IllegalArgumentException("Repository configuration is required");
}
private Concept getConcept(CloseableIteration<? extends BindingSet, QueryEvaluationException> sparqlResults) throws QueryEvaluationException {
Concept concept = new Concept();
String id = null;
while (sparqlResults.hasNext()) {
BindingSet bSet = sparqlResults.next();
LOGGER.trace("Binding set {}", bSet);
Binding o = bSet.getBinding("o");
Binding y = bSet.getBinding("y");
if ( id == null) {
Value v = bSet.getValue("x");
id = StringUtils.delete(v.stringValue(), BASE_URI);
concept.setId(id);
}
LOGGER.trace("Value for o {} and y {} ", o, y);
if (o != null && y != null) {
concept.addProperties(o.getValue(), y.getValue());
}
}
concept = StringUtils.isEmpty(concept.getId()) ? null : concept;
LOGGER.debug("Returning {}", concept);
return concept;
}
/* (non-Javadoc)
* @see org.ihtsdo.otf.snomed.service.ConceptLookupService#getTypes(String)
*/
@Override
public Map<String, String> getTypes(String id)
throws ConceptServiceException {
LOGGER.debug("getting types for given id {}", id);
Map<String, String> types = new HashMap<String, String>();
TitanGraph g = null;
Sail sail = null;
SailConnection sc = null;
try {
g = getGraph();
sail = getSail(g);
sail.initialize();
sc = sail.getConnection();
SPARQLParser parser = new SPARQLParser();
CloseableIteration<? extends BindingSet, QueryEvaluationException> sparqlResults;
ParsedQuery sparql = parser.parseQuery(Q_TYPES, "http://sct.snomed.info");
ValueFactory vf = sail.getValueFactory();
URI uri = vf.createURI(BASE_URI + id);
MapBindingSet bindings = new MapBindingSet();
bindings.addBinding("id", uri);
sparqlResults = sc.evaluate(sparql.getTupleExpr(), sparql.getDataset(), bindings, true);
while (sparqlResults.hasNext()) {
BindingSet bSet = sparqlResults.next();
LOGGER.trace("Binding set {}", bSet);
Binding typeName = bSet.getBinding("SubTypeName");
Binding typeId = bSet.getBinding("SubTypeId");
LOGGER.trace("Value for o {} and y {} ", typeName, typeId);
if ( typeName != null && typeId != null) {
types.put(StringUtils.delete(typeId.getValue().stringValue(), BASE_URI) , typeName.getValue().stringValue());
}
}
} catch (Exception e) {
LOGGER.error("Error duing concept details for concept map fetch", e);
throw new ConceptServiceException(e);
} finally {
close(sc);
close(sail);
close(g);
}
LOGGER.debug("returning total {} types ", types.size());
return Collections.unmodifiableMap(types);
}
/**
* @param config the config to set
*/
public void setConfig(Configuration config) {
this.config = config;
}
}