package org.ohdsi.webapi.service; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.ohdsi.sql.SqlRender; import org.ohdsi.sql.SqlTranslate; import org.ohdsi.webapi.activity.Activity.ActivityType; import org.ohdsi.webapi.activity.Tracker; import org.ohdsi.webapi.conceptset.ConceptSetComparison; import org.ohdsi.webapi.conceptset.ConceptSetOptimizationResult; import org.ohdsi.webapi.helper.ResourceHelper; import org.ohdsi.webapi.source.Source; import org.ohdsi.webapi.source.SourceDaimon; import org.ohdsi.webapi.source.SourceInfo; import org.ohdsi.webapi.vocabulary.Concept; import org.ohdsi.webapi.vocabulary.ConceptRelationship; import org.ohdsi.webapi.vocabulary.ConceptSearch; import org.ohdsi.webapi.vocabulary.ConceptSetExpression; import org.ohdsi.webapi.vocabulary.ConceptSetExpression.ConceptSetItem; import org.ohdsi.webapi.vocabulary.ConceptSetExpressionQueryBuilder; import org.ohdsi.webapi.vocabulary.DescendentOfAncestorSearch; import org.ohdsi.webapi.vocabulary.Domain; import org.ohdsi.webapi.vocabulary.RelatedConcept; import org.ohdsi.webapi.vocabulary.RelatedConceptSearch; import org.ohdsi.webapi.vocabulary.Vocabulary; import org.ohdsi.webapi.vocabulary.VocabularyInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; /** * @author fdefalco */ @Path("vocabulary/") @Component public class VocabularyService extends AbstractDaoService { private static Hashtable<String, VocabularyInfo> vocabularyInfoCache = null; @Autowired private SourceService sourceService; private final RowMapper<Concept> rowMapper = new RowMapper<Concept>() { @Override public Concept mapRow(final ResultSet resultSet, final int arg1) throws SQLException { final Concept concept = new Concept(); concept.conceptId = resultSet.getLong("CONCEPT_ID"); concept.conceptCode = resultSet.getString("CONCEPT_CODE"); concept.conceptName = resultSet.getString("CONCEPT_NAME"); concept.standardConcept = resultSet.getString("STANDARD_CONCEPT"); concept.invalidReason = resultSet.getString("INVALID_REASON"); concept.conceptClassId = resultSet.getString("CONCEPT_CLASS_ID"); concept.vocabularyId = resultSet.getString("VOCABULARY_ID"); concept.domainId = resultSet.getString("DOMAIN_ID"); return concept; } }; private String getDefaultVocabularySourceKey() { // fun with streams: // the below expression streams each source, returning the first (or null) which contains a daimon that is either Vocabulary or CDM SourceInfo firstSource = sourceService.getSources().stream() .filter(source -> source.daimons.stream() .filter(daimon -> daimon.getDaimonType() == SourceDaimon.DaimonType.Vocabulary || daimon.getDaimonType() == SourceDaimon.DaimonType.CDM) .collect(Collectors.toList()).size() > 0) .findFirst().orElse(null); if (firstSource != null) return firstSource.sourceKey; return null; } /** * @summary Perform a lookup of an array of concept identifiers returning the * matching concepts with their detailed properties. * @param sourceKey path parameter specifying the source key identifying the * source to use for access to the set of vocabulary tables * @param identifiers an array of concept identifiers * @return collection of concepts */ @Path("{sourceKey}/lookup/identifiers") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeIdentifierLookup(@PathParam("sourceKey") String sourceKey, long[] identifiers) { if (identifiers.length == 0) { return new ArrayList<>(); } Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/lookupIdentifiers.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"identifiers", "CDM_schema"}, new String[]{ JoinArray(identifiers), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * @summary Perform a lookup of an array of concept identifiers returning the * matching concepts with their detailed properties, using the default source. * @param identifiers an array of concept identifiers * @return collection of concepts */ @Path("lookup/identifiers") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeIdentifierLookup(long[] identifiers) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return executeIdentifierLookup(defaultSourceKey, identifiers); } public Collection<Concept> executeIncludedConceptLookup(String sourceKey, ConceptSetExpression conceptSetExpression) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); ConceptSetExpressionQueryBuilder builder = new ConceptSetExpressionQueryBuilder(); String query = builder.buildExpressionQuery(conceptSetExpression); query = SqlRender.renderSql(query, new String[]{"cdm_database_schema"}, new String[]{tableQualifier}); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/lookupIdentifiers.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"identifiers", "CDM_schema"}, new String[]{ query, tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * @summary Lookup source codes in the specified vocabulary * @param sourceKey path parameter specifying the source key identifying the * source to use for access to the set of vocabulary tables * @param sourcecodes array of source codes * @return collection of concepts */ @Path("{sourceKey}/lookup/sourcecodes") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeSourcecodeLookup(@PathParam("sourceKey") String sourceKey, String[] sourcecodes) { if (sourcecodes.length == 0) { return new ArrayList<>(); } Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); for (String sourcecode : sourcecodes) { sourcecode = "'" + sourcecode + "'"; } String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/lookupSourcecodes.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"sourcecodes", "CDM_schema"}, new String[]{ JoinArray(sourcecodes), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * @summary Lookup source codes in the specified vocabulary using the default source. * @param sourcecodes array of source codes * @return collection of concepts */ @Path("lookup/sourcecodes") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeSourcecodeLookup(String[] sourcecodes) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return executeSourcecodeLookup(defaultSourceKey, sourcecodes); } /** * @summary find all concepts mapped to the identifiers provided * @param sourceKey path parameter specifying the source key identifying the * source to use for access to the set of vocabulary tables * @param identifiers an array of concept identifiers * @return collection of concepts */ @Path("{sourceKey}/lookup/mapped") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeMappedLookup(@PathParam("sourceKey") String sourceKey, long[] identifiers) { if (identifiers.length == 0) { return new ArrayList<>(); } Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getMappedSourcecodes.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"identifiers", "CDM_schema"}, new String[]{ JoinArray(identifiers), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * @summary find all concepts mapped to the identifiers provided using the default vocabulary source. * @param identifiers an array of concept identifiers * @return collection of concepts */ @Path("lookup/mapped") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeMappedLookup(long[] identifiers) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return executeMappedLookup(defaultSourceKey, identifiers); } public Collection<Concept> executeMappedLookup(String sourceKey, ConceptSetExpression conceptSetExpression) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); ConceptSetExpressionQueryBuilder builder = new ConceptSetExpressionQueryBuilder(); String query = builder.buildExpressionQuery(conceptSetExpression); query = SqlRender.renderSql(query, new String[]{"cdm_database_schema"}, new String[]{tableQualifier}); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getMappedSourcecodes.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"identifiers", "CDM_schema"}, new String[]{ query, tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } @Path("{sourceKey}/search") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeSearch(@PathParam("sourceKey") String sourceKey, ConceptSearch search) { Tracker.trackActivity(ActivityType.Search, search.query); Source source = getSourceRepository().findBySourceKey(sourceKey); // escape single quote for queries search.query = search.query.replace("'", "''"); // escape for bracket search.query = search.query.replace("[", "[[]"); String filters = ""; if (search.domainId != null) { filters += " AND DOMAIN_ID IN (" + JoinArray(search.domainId) + ")"; } if (search.vocabularyId != null) { filters += " AND VOCABULARY_ID IN (" + JoinArray(search.vocabularyId) + ")"; } if (search.conceptClassId != null) { filters += " AND CONCEPT_CLASS_ID IN (" + JoinArray(search.conceptClassId) + ")"; } if (search.invalidReason != null) { if (search.invalidReason.equals("V")) { filters += " AND INVALID_REASON IS NULL "; } else { filters += " AND INVALID_REASON = '" + search.invalidReason + "' "; } } if (search.standardConcept != null) { if (search.standardConcept.equals("N")) { filters += " AND STANDARD_CONCEPT IS NULL "; } else { filters += " AND STANDARD_CONCEPT = '" + search.standardConcept + "'"; } } String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/search.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"query", "CDM_schema", "filters"}, new String[]{ search.query.toLowerCase(), tableQualifier, filters}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * Perform a search using the default vocabulary source. * @param search * @return */ @Path("search") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> executeSearch(ConceptSearch search) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return executeSearch(defaultSourceKey, search); } /** * @param query * @return */ @Path("{sourceKey}/search/{query}") @GET @Produces(MediaType.APPLICATION_JSON) public Collection<Concept> executeSearch(@PathParam("sourceKey") String sourceKey, @PathParam("query") String query) { Tracker.trackActivity(ActivityType.Search, query); // escape single quote for queries query = query.replace("'", "''"); query = query.replace("[", "[[]"); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/search.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"query", "CDM_schema", "filters"}, new String[]{ query.toLowerCase(), tableQualifier, ""}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } /** * Executes a search on the highest priority source that is found first * @param query * @return */ @Path("search/{query}") @GET @Produces(MediaType.APPLICATION_JSON) public Collection<Concept> executeSearch(@PathParam("query") String query) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return executeSearch(defaultSourceKey, query); } /** * @param id * @return */ @GET @Path("{sourceKey}/concept/{id}") @Produces(MediaType.APPLICATION_JSON) public Concept getConcept(@PathParam("sourceKey") final String sourceKey, @PathParam("id") final long id) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getConcept.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"id", "CDM_schema"}, new String[]{String.valueOf(id), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); Concept concept = null; try { concept = getSourceJdbcTemplate(source).queryForObject(sql_statement, this.rowMapper); } catch (EmptyResultDataAccessException e) { log.debug(String.format("Request for conceptId=%s resulted in 0 results", id)); throw new WebApplicationException(Response.Status.RESET_CONTENT); // http 205 } return concept; } /** * Returns concept details from the default vocabulary source. * @param id * @return */ @GET @Path("concept/{id}") @Produces(MediaType.APPLICATION_JSON) public Concept getConcept(@PathParam("id") final long id) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getConcept(defaultSourceKey, id); } /** * @param id * @return */ @GET @Path("{sourceKey}/concept/{id}/related") @Produces(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getRelatedConcepts(@PathParam("sourceKey") String sourceKey, @PathParam("id") final Long id) { final Map<Long, RelatedConcept> concepts = new HashMap<>(); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getRelatedConcepts.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"id", "CDM_schema"}, new String[]{String.valueOf(id), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Void>() { @Override public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { addRelationships(concepts, resultSet); return null; } }); return concepts.values(); } /** * Returns related concepts from the default vocabulary source. * @param id * @return */ @GET @Path("concept/{id}/related") @Produces(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getRelatedConcepts(@PathParam("id") final Long id) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getRelatedConcepts(defaultSourceKey, id); } @POST @Path("{sourceKey}/commonAncestors") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getCommonAncestors(@PathParam("sourceKey") String sourceKey, Object[] identifiers) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); final Map<Long, RelatedConcept> concepts = new HashMap<>(); String conceptIdentifierList = org.springframework.util.StringUtils.arrayToCommaDelimitedString(identifiers); String conceptIdentifierListLength = Integer.toString(identifiers.length); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getCommonAncestors.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"conceptIdentifierList", "conceptIdentifierListLength", "CDM_schema"}, new String[]{conceptIdentifierList, conceptIdentifierListLength, tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Void>() { @Override public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { addRelationships(concepts, resultSet); return null; } }); return concepts.values(); } @POST @Path("/commonAncestors") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getCommonAncestors(Object[] identifiers) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getCommonAncestors(defaultSourceKey, identifiers); } @POST @Path("{sourceKey}/resolveConceptSetExpression") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Long> resolveConceptSetExpression(@PathParam("sourceKey") String sourceKey, ConceptSetExpression conceptSetExpression) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); ConceptSetExpressionQueryBuilder builder = new ConceptSetExpressionQueryBuilder(); String query = builder.buildExpressionQuery(conceptSetExpression); query = SqlRender.renderSql(query, new String[]{"cdm_database_schema"}, new String[]{tableQualifier}); query = SqlTranslate.translateSql(query, "sql server", source.getSourceDialect()); final ArrayList<Long> identifiers = new ArrayList<>(); getSourceJdbcTemplate(source).query(query, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { identifiers.add(rs.getLong("CONCEPT_ID")); } }); return identifiers; } @POST @Path("resolveConceptSetExpression") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Long> resolveConceptSetExpression(ConceptSetExpression conceptSetExpression) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return resolveConceptSetExpression(defaultSourceKey, conceptSetExpression); } @POST @Path("conceptSetExpressionSQL") @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.APPLICATION_JSON) public String getConceptSetExpressionSQL(ConceptSetExpression conceptSetExpression) { ConceptSetExpressionQueryBuilder builder = new ConceptSetExpressionQueryBuilder(); String query = builder.buildExpressionQuery(conceptSetExpression); return query; } @GET @Path("{sourceKey}/concept/{id}/descendants") @Produces(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getDescendantConcepts(@PathParam("sourceKey") String sourceKey, @PathParam("id") final Long id) { final Map<Long, RelatedConcept> concepts = new HashMap<>(); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getDescendantConcepts.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"id", "CDM_schema"}, new String[]{String.valueOf(id), tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Void>() { @Override public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { addRelationships(concepts, resultSet); return null; } }); return concepts.values(); } @GET @Path("concept/{id}/descendants") @Produces(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getDescendantConcepts(@PathParam("id") final Long id) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getDescendantConcepts(defaultSourceKey, id); } @GET @Path("{sourceKey}/domains") @Produces(MediaType.APPLICATION_JSON) public Collection<Domain> getDomains(@PathParam("sourceKey") String sourceKey) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getDomains.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"CDM_schema"}, new String[]{tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Domain>() { @Override public Domain mapRow(final ResultSet resultSet, final int arg1) throws SQLException { final Domain domain = new Domain(); domain.domainId = resultSet.getString("DOMAIN_ID"); domain.domainName = resultSet.getString("DOMAIN_NAME"); domain.domainConceptId = resultSet.getLong("DOMAIN_CONCEPT_ID"); return domain; } }); } @GET @Path("domains") @Produces(MediaType.APPLICATION_JSON) public Collection<Domain> getDomains() { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getDomains(defaultSourceKey); } @GET @Path("{sourceKey}/vocabularies") @Produces(MediaType.APPLICATION_JSON) public Collection<Vocabulary> getVocabularies(@PathParam("sourceKey") String sourceKey) { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getVocabularies.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"CDM_schema"}, new String[]{tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Vocabulary>() { @Override public Vocabulary mapRow(final ResultSet resultSet, final int arg1) throws SQLException { final Vocabulary vocabulary = new Vocabulary(); vocabulary.vocabularyId = resultSet.getString("VOCABULARY_ID"); vocabulary.vocabularyName = resultSet.getString("VOCABULARY_NAME"); vocabulary.vocabularyReference = resultSet.getString("VOCABULARY_REFERENCE"); vocabulary.vocabularyVersion = resultSet.getString("VOCABULARY_VERSION"); vocabulary.vocabularyConceptId = resultSet.getLong("VOCABULARY_CONCEPT_ID"); return vocabulary; } }); } @GET @Path("vocabularies") @Produces(MediaType.APPLICATION_JSON) public Collection<Vocabulary> getVocabularies() { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getVocabularies(defaultSourceKey); } private void addRelationships(final Map<Long, RelatedConcept> concepts, final ResultSet resultSet) throws SQLException { final Long concept_id = resultSet.getLong("CONCEPT_ID"); if (!concepts.containsKey(concept_id)) { final RelatedConcept concept = new RelatedConcept(); concept.conceptId = concept_id; concept.conceptCode = resultSet.getString("CONCEPT_CODE"); concept.conceptName = resultSet.getString("CONCEPT_NAME"); concept.standardConcept = resultSet.getString("STANDARD_CONCEPT"); concept.invalidReason = resultSet.getString("INVALID_REASON"); concept.vocabularyId = resultSet.getString("VOCABULARY_ID"); concept.conceptClassId = resultSet.getString("CONCEPT_CLASS_ID"); concept.domainId = resultSet.getString("DOMAIN_ID"); final ConceptRelationship relationship = new ConceptRelationship(); relationship.relationshipName = resultSet.getString("RELATIONSHIP_NAME"); relationship.relationshipDistance = resultSet.getInt("RELATIONSHIP_DISTANCE"); concept.relationships.add(relationship); concepts.put(concept_id, concept); } else { final ConceptRelationship relationship = new ConceptRelationship(); relationship.relationshipName = resultSet.getString("RELATIONSHIP_NAME"); relationship.relationshipDistance = resultSet.getInt("RELATIONSHIP_DISTANCE"); concepts.get(concept_id).relationships.add(relationship); } } //TODO @GET @Path("{sourceKey}/info") @Produces(MediaType.APPLICATION_JSON) public VocabularyInfo getInfo(@PathParam("sourceKey") String sourceKey) { if (vocabularyInfoCache == null) { vocabularyInfoCache = new Hashtable<>(); } if (!vocabularyInfoCache.containsKey(sourceKey)) { final VocabularyInfo info = new VocabularyInfo(); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getInfo.sql"); info.dialect = source.getSourceDialect(); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"CDM_schema"}, new String[]{tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); vocabularyInfoCache.put(sourceKey, getSourceJdbcTemplate(source).queryForObject(sql_statement, new RowMapper<VocabularyInfo>() { @Override public VocabularyInfo mapRow(final ResultSet resultSet, final int arg1) throws SQLException { info.version = resultSet.getString("VOCABULARY_VERSION"); return info; } })); } return vocabularyInfoCache.get(sourceKey); } @POST @Path("{sourceKey}/descendantofancestor") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> getDescendantOfAncestorConcepts(@PathParam("sourceKey") String sourceKey, DescendentOfAncestorSearch search) { Tracker.trackActivity(ActivityType.Search, "getDescendantOfAncestorConcepts"); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getDescendentOfAncestorConcepts.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"CDM_schema", "id", "ancestorVocabularyId", "ancestorClassId", "siblingVocabularyId", "siblingClassId"}, new String[]{ tableQualifier, search.conceptId, search.ancestorVocabularyId, search.ancestorClassId, search.siblingVocabularyId, search.siblingClassId}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } @POST @Path("descendantofancestor") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> getDescendantOfAncestorConcepts(DescendentOfAncestorSearch search) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getDescendantOfAncestorConcepts(defaultSourceKey, search); } @Path("{sourceKey}/relatedconcepts") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> getRelatedConcepts(@PathParam("sourceKey") String sourceKey, RelatedConceptSearch search) { Tracker.trackActivity(ActivityType.Search, "getRelatedConcepts"); Source source = getSourceRepository().findBySourceKey(sourceKey); ArrayList<String> filterList = new ArrayList<String>(); long[] conceptIds = search.conceptId; if (search.vocabularyId != null && search.vocabularyId.length > 0) { filterList.add("VOCABULARY_ID IN (" + JoinArray(search.vocabularyId) + ")"); } if (search.conceptClassId != null) { filterList.add("CONCEPT_CLASS_ID IN (" + JoinArray(search.conceptClassId) + ")"); } String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getRelatedConceptsFiltered.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"CDM_schema", "conceptList", "filters"}, new String[]{ tableQualifier, this.JoinArray(conceptIds), this.JoinArrayList(filterList)}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); return getSourceJdbcTemplate(source).query(sql_statement, this.rowMapper); } @Path("relatedconcepts") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<Concept> getRelatedConcepts(RelatedConceptSearch search) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getRelatedConcepts(defaultSourceKey, search); } @Path("{sourceKey}/conceptlist/descendants") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getDescendantConceptsByList(@PathParam("sourceKey") String sourceKey, String[] conceptList) { final Map<Long, RelatedConcept> concepts = new HashMap<>(); Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); String conceptListForQuery = this.JoinArray(conceptList); String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/getDescendantConceptsMultipleConcepts.sql"); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"id", "CDM_schema"}, new String[]{conceptListForQuery, tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<Void>() { @Override public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { addRelationships(concepts, resultSet); return null; } }); return concepts.values(); } @Path("conceptlist/descendants") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<RelatedConcept> getDescendantConceptsByList(String[] conceptList) { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return getDescendantConceptsByList(defaultSourceKey, conceptList); } @Path("{sourceKey}/compare") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<ConceptSetComparison> compareConceptSets(@PathParam("sourceKey") String sourceKey, ConceptSetExpression[] conceptSetExpressionList) throws Exception { if (conceptSetExpressionList.length != 2) { throw new Exception("You must specify two concept set expressions in order to use this method."); } Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); // Get the comparison script String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/compareConceptSets.sql"); // Get the queries that represent the ConceptSetExpressions that were passed // into the function ConceptSetExpressionQueryBuilder builder = new ConceptSetExpressionQueryBuilder(); String cs1Query = builder.buildExpressionQuery(conceptSetExpressionList[0]); String cs2Query = builder.buildExpressionQuery(conceptSetExpressionList[1]); // Insert the queries into the overall comparison script sql_statement = SqlRender.renderSql(sql_statement, new String[]{"cs1_expression", "cs2_expression"}, new String[]{cs1Query, cs2Query}); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"cdm_database_schema"}, new String[]{tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); // Execute the query Collection<ConceptSetComparison> returnVal = getSourceJdbcTemplate(source).query(sql_statement, new RowMapper<ConceptSetComparison>() { @Override public ConceptSetComparison mapRow(ResultSet rs, int rowNum) throws SQLException { ConceptSetComparison csc = new ConceptSetComparison(); csc.conceptId = rs.getLong("concept_id"); csc.conceptIn1Only = rs.getLong("concept_in_1_only"); csc.conceptIn2Only = rs.getLong("concept_in_2_only"); csc.conceptIn1And2 = rs.getLong("concept_in_both_1_and_2"); csc.conceptName = rs.getString("concept_name"); csc.standardConcept = rs.getString("standard_concept"); csc.invalidReason = rs.getString("invalid_reason"); csc.conceptCode = rs.getString("concept_code"); csc.domainId = rs.getString("domain_id"); csc.vocabularyId = rs.getString("vocabulary_id"); csc.conceptClassId = rs.getString("concept_class_id"); return csc; } }); return returnVal; } @Path("compare") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Collection<ConceptSetComparison> compareConceptSets(ConceptSetExpression[] conceptSetExpressionList) throws Exception { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return compareConceptSets(defaultSourceKey, conceptSetExpressionList); } @Path("{sourceKey}/optimize") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public ConceptSetOptimizationResult optimizeConceptSet(@PathParam("sourceKey") String sourceKey, ConceptSetExpression conceptSetExpression) throws Exception { Source source = getSourceRepository().findBySourceKey(sourceKey); String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Vocabulary); // Get the optimization script String sql_statement = ResourceHelper.GetResourceAsString("/resources/vocabulary/sql/optimizeConceptSet.sql"); // Find all of the concepts that should be considered for optimization // Create a hashtable to hold all of the contents of the ConceptSetExpression // for use later Hashtable<String, ConceptSetExpression.ConceptSetItem> allConceptSetItems = new Hashtable<String, ConceptSetExpression.ConceptSetItem>(); ArrayList<String> includedConcepts = new ArrayList<String>(); ArrayList<String> descendantConcepts = new ArrayList<String>(); ArrayList<String> allOtherConcepts = new ArrayList<String>(); for(ConceptSetExpression.ConceptSetItem item : conceptSetExpression.items) { allConceptSetItems.put(item.concept.conceptId.toString(), item); if (!item.isExcluded) { includedConcepts.add(item.concept.conceptId.toString()); if (item.includeDescendants) { descendantConcepts.add(item.concept.conceptId.toString()); } } else { allOtherConcepts.add(item.concept.conceptId.toString()); } } // If no descendant concepts are specified, initialize this field to use concept_id = 0 so the query will work properly if (descendantConcepts.isEmpty()) descendantConcepts.add("0"); String allConceptsList = this.JoinArray(includedConcepts.toArray(new String[includedConcepts.size()])); String descendantConceptsList = this.JoinArray(descendantConcepts.toArray(new String[descendantConcepts.size()])); sql_statement = SqlRender.renderSql(sql_statement, new String[]{"allConcepts", "descendantConcepts", "cdm_database_schema"}, new String[]{allConceptsList, descendantConceptsList, tableQualifier}); sql_statement = SqlTranslate.translateSql(sql_statement, "sql server", source.getSourceDialect()); // Execute the query to obtain a result set that contains the // most optimized version of the concept set. Then, using these results, // construct a new ConceptSetExpression object that only contains the // concepts that were identified as optimal to achieve the same definition ConceptSetOptimizationResult returnVal = new ConceptSetOptimizationResult(); ArrayList<ConceptSetExpression.ConceptSetItem> optimzedExpressionItems = new ArrayList<>(); ArrayList<ConceptSetExpression.ConceptSetItem> removedExpressionItems = new ArrayList<>(); List<Map<String, Object>> rows = getSourceJdbcTemplate(source).queryForList(sql_statement); for (Map rs : rows) { String conceptId = String.valueOf(rs.get("concept_id")); String removed = String.valueOf(rs.get("removed")); ConceptSetExpression.ConceptSetItem csi = allConceptSetItems.get(conceptId); if (removed.equals("0")) { optimzedExpressionItems.add(csi); } else { removedExpressionItems.add(csi); } } // Re-add back the other concepts that are not considered // as part of the optimizatin process for(String conceptId : allOtherConcepts) { ConceptSetExpression.ConceptSetItem csi = allConceptSetItems.get(conceptId); optimzedExpressionItems.add(csi); } returnVal.optimizedConceptSet.items = optimzedExpressionItems.toArray(new ConceptSetExpression.ConceptSetItem[optimzedExpressionItems.size()]); returnVal.removedConceptSet.items = removedExpressionItems.toArray(new ConceptSetExpression.ConceptSetItem[removedExpressionItems.size()]); return returnVal; } @Path("optimize") @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public ConceptSetOptimizationResult optimizeConceptSet(ConceptSetExpression conceptSetExpression) throws Exception { String defaultSourceKey = getDefaultVocabularySourceKey(); if (defaultSourceKey == null) throw new WebApplicationException(new Exception("No vocabulary or cdm daimon was found in configured sources. Search failed."), Response.Status.SERVICE_UNAVAILABLE); // http 503 return optimizeConceptSet(defaultSourceKey, conceptSetExpression); } private String JoinArray(final long[] array) { String result = ""; for (int i = 0; i < array.length; i++) { if (i > 0) { result += ","; } result += array[i]; } return result; } private String JoinArray(final String[] array) { String result = ""; for (int i = 0; i < array.length; i++) { if (i > 0) { result += ","; } result += "'" + array[i] + "'"; } return result; } private String JoinArrayList(final ArrayList<String> array){ String result = ""; for (int i = 0; i < array.size(); i++) { if (i > 0) { result += " AND "; } result += array.get(i); } return result; } }