diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/FHIRTermGraph.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/FHIRTermGraph.java index b42292a166b..90329e9548d 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/FHIRTermGraph.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/FHIRTermGraph.java @@ -28,4 +28,5 @@ default Stream> indexQuery(String query) { Stream> indexQuery(String query, int limit); void close(); void drop(); + void dropAllVertices(); } diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/impl/FHIRTermGraphImpl.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/impl/FHIRTermGraphImpl.java index fa6151b9064..7a01ff53904 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/impl/FHIRTermGraphImpl.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/impl/FHIRTermGraphImpl.java @@ -194,4 +194,13 @@ public void drop() { graph = null; } } + + @Override + public void dropAllVertices() { + log.info("Dropping all vertices..."); + if (traversal != null) { + traversal.V().drop().iterate(); + traversal.tx().commit(); + } + } } diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/FHIRTermGraphLoader.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/FHIRTermGraphLoader.java index d5da686b99b..f6b1561eaab 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/FHIRTermGraphLoader.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/FHIRTermGraphLoader.java @@ -10,6 +10,8 @@ import org.apache.commons.cli.Options; +import com.ibm.fhir.term.graph.FHIRTermGraph; + public interface FHIRTermGraphLoader { enum Type { CODESYSTEM { @@ -49,4 +51,5 @@ public Options options() { void load(); void close(); Map options(); + FHIRTermGraph getGraph(); } diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/AbstractTermGraphLoader.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/AbstractTermGraphLoader.java index 6d7cf6ffa79..5273502c78e 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/AbstractTermGraphLoader.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/AbstractTermGraphLoader.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Objects; +import org.apache.commons.configuration.Configuration; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.janusgraph.core.JanusGraph; @@ -35,11 +36,30 @@ public AbstractTermGraphLoader(Map options) { g = graph.traversal(); // create label filter - if (options.containsKey("labels")) { - labelFilter = new LabelFilter(new HashSet<>(Arrays.asList(options.get("labels").split(",")))); - } else { - labelFilter = LabelFilter.ACCEPT_ALL; - } + labelFilter = createLabelFilter(options); + } + + public AbstractTermGraphLoader(Map options, Configuration configuration) { + this.options = Objects.requireNonNull(options, "options"); + Objects.requireNonNull(configuration, "configuration"); + + graph = FHIRTermGraphFactory.open(configuration); + janusGraph = graph.getJanusGraph(); + g = graph.traversal(); + + // create label filter + labelFilter = createLabelFilter(options); + } + + public AbstractTermGraphLoader(Map options, FHIRTermGraph graph) { + this.options = Objects.requireNonNull(options, "options"); + + this.graph = Objects.requireNonNull(graph, "graph"); + janusGraph = graph.getJanusGraph(); + g = graph.traversal(); + + // create label filter + labelFilter = createLabelFilter(options); } @Override @@ -54,4 +74,15 @@ public final void close() { public final Map options() { return options; } + + @Override + public final FHIRTermGraph getGraph() { + return graph; + } + + protected LabelFilter createLabelFilter(Map options) { + return options.containsKey("labels") ? + new LabelFilter(new HashSet<>(Arrays.asList(options.get("labels").split(",")))) : + LabelFilter.ACCEPT_ALL; + } } diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/CodeSystemTermGraphLoader.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/CodeSystemTermGraphLoader.java index b4bfd2a555f..45a1d58cd9d 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/CodeSystemTermGraphLoader.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/loader/impl/CodeSystemTermGraphLoader.java @@ -7,14 +7,14 @@ package com.ibm.fhir.term.graph.loader.impl; import static com.ibm.fhir.term.graph.loader.util.FHIRTermGraphLoaderUtil.toMap; -import static com.ibm.fhir.term.graph.util.FHIRTermGraphUtil.toObject; import static com.ibm.fhir.term.graph.util.FHIRTermGraphUtil.normalize; import static com.ibm.fhir.term.graph.util.FHIRTermGraphUtil.toLong; +import static com.ibm.fhir.term.graph.util.FHIRTermGraphUtil.toObject; import static com.ibm.fhir.term.util.CodeSystemSupport.getConcepts; -import static com.ibm.fhir.term.util.CodeSystemSupport.isCaseSensitive; import java.io.FileInputStream; import java.io.InputStream; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -27,6 +27,7 @@ import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.MissingOptionException; import org.apache.commons.cli.Options; +import org.apache.commons.configuration.Configuration; import org.apache.tinkerpop.gremlin.structure.Vertex; import com.ibm.fhir.model.format.Format; @@ -72,6 +73,22 @@ public CodeSystemTermGraphLoader(Map options) { conceptVertexMap = new HashMap<>(); } + public CodeSystemTermGraphLoader(Configuration configuration, CodeSystem codeSystem) { + super(Collections.emptyMap(), configuration); + + this.codeSystem = Objects.requireNonNull(codeSystem, "codeSystem"); + concepts = getConcepts(codeSystem); + conceptVertexMap = new HashMap<>(); + } + + public CodeSystemTermGraphLoader(FHIRTermGraph graph, CodeSystem codeSystem) { + super(Collections.emptyMap(), graph); + + this.codeSystem = Objects.requireNonNull(codeSystem, "codeSystem"); + concepts = getConcepts(codeSystem); + conceptVertexMap = new HashMap<>(); + } + @Override public void load() { createCodeSystemVertex(); @@ -88,7 +105,7 @@ private void createCodeSystemVertex() { codeSystemVertex = g.addV("CodeSystem") .property("url", url) - .property("caseSensitive", isCaseSensitive(codeSystem)) + .property("count", concepts.size()) .next(); if (codeSystem.getVersion() != null) { diff --git a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/provider/GraphTermServiceProvider.java b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/provider/GraphTermServiceProvider.java index ab7c5fe7c16..75f4f2f0f8b 100644 --- a/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/provider/GraphTermServiceProvider.java +++ b/fhir-term-graph/src/main/java/com/ibm/fhir/term/graph/provider/GraphTermServiceProvider.java @@ -53,12 +53,18 @@ import com.ibm.fhir.term.spi.FHIRTermServiceProvider; public class GraphTermServiceProvider implements FHIRTermServiceProvider { + private static final int DEFAULT_COUNT = 1000; private final FHIRTermGraph graph; public GraphTermServiceProvider(Configuration configuration) { + Objects.requireNonNull(configuration, "configuration"); graph = FHIRTermGraphFactory.open(configuration); } + public GraphTermServiceProvider(FHIRTermGraph graph) { + this.graph = Objects.requireNonNull(graph, "graph"); + } + @Override public Set closure(CodeSystem codeSystem, Code code) { Objects.requireNonNull(codeSystem.getUrl(), "CodeSystem.url"); @@ -84,13 +90,13 @@ public Concept getConcept(CodeSystem codeSystem, Code code) { @Override public Set getConcepts(CodeSystem codeSystem) { Objects.requireNonNull(codeSystem.getUrl(), "CodeSystem.url"); - List concepts = new ArrayList<>(getCount(codeSystem)); + Set concepts = new LinkedHashSet<>(getCount(codeSystem)); hasVersion(hasUrl(vertices(), codeSystem.getUrl()), codeSystem.getVersion()) .out("concept") .elementMap() .toStream() .forEach(elementMap -> concepts.add(createConcept(elementMap))); - return Collections.emptySet(); + return concepts; } @SuppressWarnings("unchecked") @@ -325,7 +331,7 @@ private int getCount(CodeSystem codeSystem) { if (optional.isPresent()) { return (Integer) optional.get().get("count"); } - return -1; + return DEFAULT_COUNT; } private List getDesignations(CodeSystem codeSystem, String code) { @@ -393,18 +399,12 @@ private Element getValue(Map elementMap) { } private GraphTraversal hasCode(GraphTraversal g, String code, boolean caseSensitive) { - if (caseSensitive) { - return g.has("code", code); - } - return g.has("codeLowerCase", normalize(code)); + return caseSensitive ? g.has("code", code) : g.has("codeLowerCase", normalize(code)); } // anonymous graph traversal private GraphTraversal hasCode(String code, boolean caseSensitive) { - if (caseSensitive) { - return __.has("code", code); - } - return __.has("codeLowerCase", normalize(code)); + return caseSensitive ? __.has("code", code) : __.has("codeLowerCase", normalize(code)); } private GraphTraversal hasUrl(GraphTraversal g, Uri url) { @@ -412,10 +412,7 @@ private GraphTraversal hasUrl(GraphTraversal g, } private GraphTraversal hasVersion(GraphTraversal g, com.ibm.fhir.model.type.String version) { - if (version != null) { - return g.has("version", version.getValue()); - } - return g; + return (version != null) ? g.has("version", version.getValue()) : g; } private GraphTraversal vertices(Object... vertexIds) { diff --git a/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/CodeSystemTermGraphLoaderTest.java b/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/CodeSystemTermGraphLoaderTest.java index 135d11a602d..8ac1842335f 100644 --- a/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/CodeSystemTermGraphLoaderTest.java +++ b/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/CodeSystemTermGraphLoaderTest.java @@ -7,22 +7,62 @@ package com.ibm.fhir.term.graph.test; import java.io.InputStream; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import org.apache.commons.configuration.PropertiesConfiguration; +import org.locationtech.jts.util.Assert; +import org.testng.annotations.Test; import com.ibm.fhir.model.format.Format; import com.ibm.fhir.model.parser.FHIRParser; import com.ibm.fhir.model.resource.CodeSystem; -import com.ibm.fhir.model.type.Code; +import com.ibm.fhir.model.resource.CodeSystem.Concept; +import com.ibm.fhir.term.graph.FHIRTermGraph; +import com.ibm.fhir.term.graph.factory.FHIRTermGraphFactory; +import com.ibm.fhir.term.graph.loader.FHIRTermGraphLoader; +import com.ibm.fhir.term.graph.loader.impl.CodeSystemTermGraphLoader; import com.ibm.fhir.term.graph.provider.GraphTermServiceProvider; +import com.ibm.fhir.term.graph.util.FHIRTermGraphUtil; +import com.ibm.fhir.term.spi.FHIRTermServiceProvider; +import com.ibm.fhir.term.util.CodeSystemSupport; + +import ch.qos.logback.classic.Level; public class CodeSystemTermGraphLoaderTest { - public static void main(String[] args) throws Exception { - InputStream in = CodeSystemTermGraphLoaderTest.class.getClassLoader().getResourceAsStream("JSON/CodeSystem-cs5.json"); - CodeSystem codeSystem = FHIRParser.parser(Format.JSON).parse(in); - GraphTermServiceProvider provider = new GraphTermServiceProvider(new PropertiesConfiguration("conf/janusgraph-berkeleyje-lucene.properties")); - System.out.println(provider.subsumes(codeSystem, Code.of("m"), Code.of("p"))); - System.out.println(provider.getConcept(codeSystem, Code.of("o"))); - provider.getGraph().close(); + @Test + public void testCodeSystemTermGraphLoader() throws Exception { + FHIRTermGraphUtil.setRootLoggerLevel(Level.INFO); + + FHIRTermGraph graph = null; + try (InputStream in = CodeSystemTermGraphLoaderTest.class.getClassLoader().getResourceAsStream("JSON/CodeSystem-cs5.json")) { + graph = FHIRTermGraphFactory.open(new PropertiesConfiguration("conf/janusgraph-berkeleyje-lucene.properties")); + graph.dropAllVertices(); + + CodeSystem codeSystem = FHIRParser.parser(Format.JSON).parse(in); + FHIRTermGraphLoader loader = new CodeSystemTermGraphLoader(graph, codeSystem); + loader.load(); + + FHIRTermServiceProvider provider = new GraphTermServiceProvider(graph); + + Set expected = new LinkedHashSet<>(); + for (Concept concept : CodeSystemSupport.getConcepts(codeSystem)) { + expected.add(concept.toBuilder() + .concept(Collections.emptyList()) + .build()); + } + + Set actual = new LinkedHashSet<>(); + for (Concept concept : provider.getConcepts(codeSystem)) { + actual.add(provider.getConcept(codeSystem, concept.getCode())); + } + + Assert.equals(expected, actual); + } finally { + if (graph != null) { + graph.close(); + } + } } -} +} \ No newline at end of file diff --git a/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/FHIRTermGraphTest.java b/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/FHIRTermGraphTest.java index 5bb24d7b156..25d99873259 100644 --- a/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/FHIRTermGraphTest.java +++ b/fhir-term-graph/src/test/java/com/ibm/fhir/term/graph/test/FHIRTermGraphTest.java @@ -29,7 +29,7 @@ public static void main(String[] args) throws Exception { GraphTraversalSource g = graph.traversal(); g.V().drop().iterate(); - g.E().drop().iterate(); +// g.E().drop().iterate(); Vertex v1 = g.addV("Concept").property("code", "a").next(); System.out.println(v1.id()); diff --git a/fhir-term/src/main/java/com/ibm/fhir/term/util/CodeSystemSupport.java b/fhir-term/src/main/java/com/ibm/fhir/term/util/CodeSystemSupport.java index 73afe724f2a..90b4377b485 100644 --- a/fhir-term/src/main/java/com/ibm/fhir/term/util/CodeSystemSupport.java +++ b/fhir-term/src/main/java/com/ibm/fhir/term/util/CodeSystemSupport.java @@ -22,7 +22,7 @@ import com.ibm.fhir.model.resource.CodeSystem; import com.ibm.fhir.model.resource.CodeSystem.Concept; -import com.ibm.fhir.model.resource.ValueSet.Compose.Include.Filter; +import com.ibm.fhir.model.resource.ValueSet.Compose.Include; import com.ibm.fhir.model.type.Boolean; import com.ibm.fhir.model.type.Code; import com.ibm.fhir.model.type.DateTime; @@ -31,6 +31,7 @@ import com.ibm.fhir.model.type.Integer; import com.ibm.fhir.model.type.String; import com.ibm.fhir.model.type.code.CodeSystemHierarchyMeaning; +import com.ibm.fhir.model.type.code.FilterOperator; import com.ibm.fhir.model.type.code.PropertyType; import com.ibm.fhir.registry.FHIRRegistry; @@ -95,6 +96,10 @@ public static Concept findConcept(CodeSystem codeSystem, Concept concept, Code c return result; } + public static boolean hasCodeSystemFilter(CodeSystem codeSystem, Code code, FilterOperator operator) { + return getCodeSystemFilter(codeSystem, code, operator) != null; + } + /** * Determine whether a code system property with the specified code exists in the * provided code system. @@ -164,6 +169,15 @@ public static CodeSystem getCodeSystem(java.lang.String url) { return FHIRRegistry.getInstance().getResource(url, CodeSystem.class); } + public static CodeSystem.Filter getCodeSystemFilter(CodeSystem codeSystem, Code code, FilterOperator operator) { + for (CodeSystem.Filter filter : codeSystem.getFilter()) { + if (filter.getCode().equals(code) && filter.getOperator().contains(operator)) { + return filter; + } + } + return null; + } + /** * Get the code system property that matches the specified code. * @@ -263,7 +277,7 @@ public static Set getConcepts(CodeSystem codeSystem) { * @return * flattened / filtered list of Concept instances for the given code system */ - public static Set getConcepts(CodeSystem codeSystem, List filters) { + public static Set getConcepts(CodeSystem codeSystem, List filters) { Set concepts = new LinkedHashSet<>(); List conceptFilters = buildConceptFilters(codeSystem, filters); for (Concept concept : getConcepts(codeSystem)) { @@ -328,9 +342,9 @@ private static boolean accept(List conceptFilters, Concept concep return true; } - private static List buildConceptFilters(CodeSystem codeSystem, List filters) { + private static List buildConceptFilters(CodeSystem codeSystem, List filters) { List conceptFilters = new ArrayList<>(filters.size()); - for (Filter filter : filters) { + for (Include.Filter filter : filters) { ConceptFilter conceptFilter = null; switch (filter.getOp().getValueAsEnumConstant()) { case DESCENDENT_OF: @@ -374,7 +388,7 @@ private static Code code(String value) { return Code.of(value.getValue()); } - private static ConceptFilter createDescendentOfFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createDescendentOfFilter(CodeSystem codeSystem, Include.Filter filter) { if ("concept".equals(filter.getProperty().getValue()) && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) { Concept concept = findConcept(codeSystem, code(filter.getValue())); if (concept != null) { @@ -384,7 +398,7 @@ private static ConceptFilter createDescendentOfFilter(CodeSystem codeSystem, Fil return null; } - private static ConceptFilter createEqualsFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createEqualsFilter(CodeSystem codeSystem, Include.Filter filter) { Code property = filter.getProperty(); if ((("parent".equals(property.getValue()) || "child".equals(property.getValue())) && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) || (hasCodeSystemProperty(codeSystem, property) && !PropertyType.CODING.equals(getCodeSystemPropertyType(codeSystem, property)))) { @@ -393,7 +407,7 @@ private static ConceptFilter createEqualsFilter(CodeSystem codeSystem, Filter fi return null; } - private static ConceptFilter createExistsFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createExistsFilter(CodeSystem codeSystem, Include.Filter filter) { Code property = filter.getProperty(); String value = filter.getValue(); if (hasCodeSystemProperty(codeSystem, property) && convertsToBoolean(value)) { @@ -402,7 +416,7 @@ private static ConceptFilter createExistsFilter(CodeSystem codeSystem, Filter fi return null; } - private static ConceptFilter createGeneralizesFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createGeneralizesFilter(CodeSystem codeSystem, Include.Filter filter) { if ("concept".equals(filter.getProperty().getValue()) && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) { Concept concept = findConcept(codeSystem, code(filter.getValue())); if (concept != null) { @@ -412,7 +426,7 @@ private static ConceptFilter createGeneralizesFilter(CodeSystem codeSystem, Filt return null; } - private static ConceptFilter createInFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createInFilter(CodeSystem codeSystem, Include.Filter filter) { Code property = filter.getProperty(); if ("concept".equals(property.getValue()) || hasCodeSystemProperty(codeSystem, property)) { return new InFilter(property, Arrays.asList(filter.getValue().getValue().split(",")).stream() @@ -422,7 +436,7 @@ private static ConceptFilter createInFilter(CodeSystem codeSystem, Filter filter return null; } - private static ConceptFilter createIsAFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createIsAFilter(CodeSystem codeSystem, Include.Filter filter) { if ("concept".equals(filter.getProperty().getValue()) && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) { Concept concept = findConcept(codeSystem, code(filter.getValue())); if (concept != null) { @@ -432,7 +446,7 @@ private static ConceptFilter createIsAFilter(CodeSystem codeSystem, Filter filte return null; } - private static ConceptFilter createIsNotAFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createIsNotAFilter(CodeSystem codeSystem, Include.Filter filter) { if ("concept".equals(filter.getProperty().getValue()) && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) { Concept concept = findConcept(codeSystem, code(filter.getValue())); if (concept != null) { @@ -442,7 +456,7 @@ private static ConceptFilter createIsNotAFilter(CodeSystem codeSystem, Filter fi return null; } - private static ConceptFilter createNotInFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createNotInFilter(CodeSystem codeSystem, Include.Filter filter) { Code property = filter.getProperty(); if ("concept".equals(property.getValue()) || hasCodeSystemProperty(codeSystem, property)) { return new NotInFilter(property, Arrays.asList(filter.getValue().getValue().split(",")).stream() @@ -452,7 +466,7 @@ private static ConceptFilter createNotInFilter(CodeSystem codeSystem, Filter fil return null; } - private static ConceptFilter createRegexFilter(CodeSystem codeSystem, Filter filter) { + private static ConceptFilter createRegexFilter(CodeSystem codeSystem, Include.Filter filter) { Code property = filter.getProperty(); if (hasCodeSystemProperty(codeSystem, property) && PropertyType.STRING.equals(getCodeSystemPropertyType(codeSystem, property))) { return new RegexFilter(property, filter.getValue());