Updates to GraphQL

This commit is contained in:
jamesagnew 2022-01-30 17:04:10 -05:00
parent f69a30421f
commit 846866472b
8 changed files with 379 additions and 273 deletions

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.dstu3.utils; package org.hl7.fhir.dstu3.utils;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -328,7 +328,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -353,10 +353,27 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -365,8 +382,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -526,7 +543,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.r4.utils; package org.hl7.fhir.r4.utils;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -190,7 +190,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -215,10 +215,28 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and " + arg.getName() + " = '" + vl.get(0).toString() + "'"); fp.append(" and " + arg.getName() + " = '" + vl.get(0).toString() + "'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -228,8 +246,8 @@ public class GraphQLEngine implements IGraphQLEngine {
i++; i++;
} }
else { else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -389,7 +407,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property " + sel.getField().getName() + " on " + source.fhirType()); throw new EGraphQLException("Unknown property " + sel.getField().getName() + " on " + source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -3,6 +3,7 @@
<test name="filter-fhirpath" source="filter-fhirpath.gql" output="filter-fhirpath.json" context="Patient/example/$graphql"/> <test name="filter-fhirpath" source="filter-fhirpath.gql" output="filter-fhirpath.json" context="Patient/example/$graphql"/>
<test name="using wrong field" source="wrong-field.gql" output="$error" context="Patient/example/$graphql"/> <test name="using wrong field" source="wrong-field.gql" output="$error" context="Patient/example/$graphql"/>
<test name="polymorphic" source="polymorphic.gql" output="polymorphic.json" context="Observation/example/$graphql"/> <test name="polymorphic" source="polymorphic.gql" output="polymorphic.json" context="Observation/example/$graphql"/>
<test name="polymorphic-difftype" source="polymorphic-difftype.gql" output="polymorphic-difftype.json" context="Observation/example/$graphql"/>
<test name="reference" source="reference.gql" output="reference.json" context="Observation/example/$graphql"/> <test name="reference" source="reference.gql" output="reference.json" context="Observation/example/$graphql"/>
<test name="reference-type-in" source="reference-type-in.gql" output="reference-type-in.json" context="Observation/example/$graphql"/> <test name="reference-type-in" source="reference-type-in.gql" output="reference-type-in.json" context="Observation/example/$graphql"/>
<test name="reference-type-out" source="reference-type-out.gql" output="reference-type-out.json" context="Observation/example/$graphql"/> <test name="reference-type-out" source="reference-type-out.gql" output="reference-type-out.json" context="Observation/example/$graphql"/>

View File

@ -0,0 +1,5 @@
{
subject{reference}
valueQuantity {value unit}
}

View File

@ -0,0 +1,6 @@
{
"subject" : {
"reference" : "Patient/example"
},
"valueString": "FOO"
}

View File

@ -7,34 +7,34 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -356,7 +356,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -381,10 +381,28 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -393,8 +411,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -554,7 +572,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -7,34 +7,34 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. POSSIBILITY OF SUCH DAMAGE.
*/ */
@ -356,7 +356,7 @@ public class GraphQLEngine implements IGraphQLEngine {
return obj.primitiveValue() != ""; return obj.primitiveValue() != "";
} }
private List<Base> filter(Resource context, Property prop, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException { private List<Base> filter(Resource context, Property prop, String fieldName, List<Argument> arguments, List<Base> values, boolean extensionMode) throws FHIRException, EGraphQLException {
List<Base> result = new ArrayList<Base>(); List<Base> result = new ArrayList<Base>();
if (values.size() > 0) { if (values.size() > 0) {
int count = Integer.MAX_VALUE; int count = Integer.MAX_VALUE;
@ -381,10 +381,27 @@ public class GraphQLEngine implements IGraphQLEngine {
fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'"); fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
} }
} }
// Account for situations where the GraphQL expression selected e.g.
// effectiveDateTime but the field contains effectivePeriod
String propName = prop.getName();
List<Base> newValues = new ArrayList<>(values.size());
for (Base value : values) {
if (propName.endsWith("[x]")) {
String propNameShortened = propName.substring(0, propName.length() - 3);
if (fieldName.startsWith(propNameShortened) && fieldName.length() > propNameShortened.length()) {
if (!value.fhirType().equalsIgnoreCase(fieldName.substring(propNameShortened.length()))) {
continue;
}
}
}
newValues.add(value);
}
int i = 0; int i = 0;
int t = 0; int t = 0;
if (fp.length() == 0) if (fp.length() == 0)
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode)) { if ((i >= offset) && passesExtensionMode(v, extensionMode)) {
result.add(v); result.add(v);
t++; t++;
@ -393,8 +410,8 @@ public class GraphQLEngine implements IGraphQLEngine {
} }
i++; i++;
} else { } else {
ExpressionNode node = fpe.parse(fp.toString().substring(5)); ExpressionNode node = fpe.parse(fp.substring(5));
for (Base v : values) { for (Base v : newValues) {
if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) { if ((i >= offset) && passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node)) {
result.add(v); result.add(v);
t++; t++;
@ -554,7 +571,7 @@ public class GraphQLEngine implements IGraphQLEngine {
if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_")) if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType()); throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
List<Base> vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_")); List<Base> vl = filter(context, prop, sel.getField().getName(), sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
if (!vl.isEmpty()) if (!vl.isEmpty())
processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix); processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"), inheritedList, suffix);
} }

View File

@ -30,24 +30,10 @@ package org.hl7.fhir.r5.utils;
*/ */
// todo: // todo:
// - generate sort order parameters // - generate sort order parameters
// - generate inherited search parameters // - generate inherited search parameters
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities; import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext;
@ -60,33 +46,50 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Objects.requireNonNull;
public class GraphQLSchemaGenerator { public class GraphQLSchemaGenerator {
public enum FHIROperationType {READ, SEARCH, CREATE, UPDATE, DELETE};
private static final String INNER_TYPE_NAME = "gql.type.name";
private static final Set<String> JSON_NUMBER_TYPES = new HashSet<String>() {{ private static final Set<String> JSON_NUMBER_TYPES = new HashSet<String>() {{
add("decimal"); add("decimal");
add("positiveInt"); add("positiveInt");
add("unsignedInt"); add("unsignedInt");
}}; }};
private final ProfileUtilities profileUtilities;
private final String version;
IWorkerContext context; IWorkerContext context;
private ProfileUtilities profileUtilities;
private String version;
public GraphQLSchemaGenerator(IWorkerContext context, String version) { public GraphQLSchemaGenerator(IWorkerContext context, String version) {
super(); super();
this.context = context; this.context = context;
this.version = version; this.version = version;
profileUtilities = new ProfileUtilities(context, null, null); profileUtilities = new ProfileUtilities(context, null, null);
} }
public void generateTypes(OutputStream stream) throws IOException, FHIRException { public void generateTypes(OutputStream stream) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
generateTypes(writer);
Map<String, StructureDefinition> pl = new HashMap<String, StructureDefinition>(); writer.flush();
Map<String, StructureDefinition> tl = new HashMap<String, StructureDefinition>(); writer.close();
}
public void generateTypes(Writer writer) throws IOException {
Map<String, StructureDefinition> pl = new HashMap<>();
Map<String, StructureDefinition> tl = new HashMap<>();
Map<String, String> existingTypeNames = new HashMap<>();
for (StructureDefinition sd : context.allStructures()) { for (StructureDefinition sd : context.allStructures()) {
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
pl.put(sd.getName(), sd); pl.put(sd.getName(), sd);
@ -95,29 +98,39 @@ public class GraphQLSchemaGenerator {
tl.put(sd.getName(), sd); tl.put(sd.getName(), sd);
} }
} }
writer.write("# FHIR GraphQL Schema. Version "+version+"\r\n\r\n"); writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n");
writer.write("# FHIR Defined Primitive types\r\n"); writer.write("# FHIR Defined Primitive types\r\n");
for (String n : sorted(pl.keySet())) for (String n : sorted(pl.keySet()))
generatePrimitive(writer, pl.get(n)); generatePrimitive(writer, pl.get(n));
writer.write("\r\n"); writer.write("\r\n");
writer.write("# FHIR Defined Search Parameter Types\r\n"); writer.write("# FHIR Defined Search Parameter Types\r\n");
for (SearchParamType dir : SearchParamType.values()) { for (SearchParamType dir : SearchParamType.values()) {
if (pl.containsKey(dir.toCode())) {
// Don't double create String and URI
continue;
}
if (dir != SearchParamType.NULL) if (dir != SearchParamType.NULL)
generateSearchParamType(writer, dir.toCode()); generateSearchParamType(writer, dir.toCode());
} }
writer.write("\r\n"); writer.write("\r\n");
generateElementBase(writer); generateElementBase(writer);
for (String n : sorted(tl.keySet())) for (String n : sorted(tl.keySet()))
generateType(writer, tl.get(n)); generateType(existingTypeNames, writer, tl.get(n));
writer.flush();
writer.close();
} }
public void generateResource(OutputStream stream, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException, FHIRException { public void generateResource(OutputStream stream, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException, FHIRException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream)); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));
writer.write("# FHIR GraphQL Schema. Version "+version+"\r\n\r\n"); generateResource(writer, sd, parameters, operations);
writer.flush();
writer.close();
}
public void generateResource(Writer writer, StructureDefinition sd, List<SearchParameter> parameters, EnumSet<FHIROperationType> operations) throws IOException {
Map<String, String> existingTypeNames = new HashMap<>();
writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n");
writer.write("# import * from 'types.graphql'\r\n\r\n"); writer.write("# import * from 'types.graphql'\r\n\r\n");
generateType(writer, sd); generateType(existingTypeNames, writer, sd);
if (operations.contains(FHIROperationType.READ)) if (operations.contains(FHIROperationType.READ))
generateIdAccess(writer, sd.getName()); generateIdAccess(writer, sd.getName());
if (operations.contains(FHIROperationType.SEARCH)) { if (operations.contains(FHIROperationType.SEARCH)) {
@ -130,93 +143,90 @@ public class GraphQLSchemaGenerator {
generateUpdate(writer, sd.getName()); generateUpdate(writer, sd.getName());
if (operations.contains(FHIROperationType.DELETE)) if (operations.contains(FHIROperationType.DELETE))
generateDelete(writer, sd.getName()); generateDelete(writer, sd.getName());
writer.flush();
writer.close();
} }
private void generateCreate(BufferedWriter writer, String name) throws IOException { private void generateCreate(Writer writer, String name) throws IOException {
writer.write("type "+name+"CreateType {\r\n"); writer.write("type " + name + "CreateType {\r\n");
writer.write(" "+name+"Create("); writer.write(" " + name + "Create(");
param(writer, "resource", name+"Input", false, false); param(writer, "resource", name + "Input", false, false);
writer.write("): "+name+"Creation\r\n"); writer.write("): " + name + "Creation\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type "+name+"Creation {\r\n"); writer.write("type " + name + "Creation {\r\n");
writer.write(" location: String\r\n"); writer.write(" location: String\r\n");
writer.write(" resource: "+name+"\r\n"); writer.write(" resource: " + name + "\r\n");
writer.write(" information: OperationOutcome\r\n"); writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateUpdate(BufferedWriter writer, String name) throws IOException { private void generateUpdate(Writer writer, String name) throws IOException {
writer.write("type "+name+"UpdateType {\r\n"); writer.write("type " + name + "UpdateType {\r\n");
writer.write(" "+name+"Update("); writer.write(" " + name + "Update(");
param(writer, "id", "ID", false, false); param(writer, "id", "ID", false, false);
writer.write(", "); writer.write(", ");
param(writer, "resource", name+"Input", false, false); param(writer, "resource", name + "Input", false, false);
writer.write("): "+name+"Update\r\n"); writer.write("): " + name + "Update\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type "+name+"Update {\r\n"); writer.write("type " + name + "Update {\r\n");
writer.write(" resource: "+name+"\r\n"); writer.write(" resource: " + name + "\r\n");
writer.write(" information: OperationOutcome\r\n"); writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateDelete(BufferedWriter writer, String name) throws IOException { private void generateDelete(Writer writer, String name) throws IOException {
writer.write("type "+name+"DeleteType {\r\n"); writer.write("type " + name + "DeleteType {\r\n");
writer.write(" "+name+"Delete("); writer.write(" " + name + "Delete(");
param(writer, "id", "ID", false, false); param(writer, "id", "ID", false, false);
writer.write("): "+name+"Delete\r\n"); writer.write("): " + name + "Delete\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type "+name+"Delete {\r\n"); writer.write("type " + name + "Delete {\r\n");
writer.write(" information: OperationOutcome\r\n"); writer.write(" information: OperationOutcome\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateListAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException { private void generateListAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type "+name+"ListType {\r\n"); writer.write("type " + name + "ListType {\r\n");
writer.write(" "+name+"List("); writer.write(" ");
generateListAccessQuery(writer, parameters, name);
writer.write("}\r\n");
writer.write("\r\n");
}
public void generateListAccessQuery(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write(name + "List(");
param(writer, "_filter", "String", false, false); param(writer, "_filter", "String", false, false);
for (SearchParameter sp : parameters) for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(sp.getType().toCode()), true, true); param(writer, sp.getName().replace("-", "_"), getGqlname(requireNonNull(sp.getType().toCode())), true, true);
param(writer, "_sort", "String", false, true); param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true); param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true); param(writer, "_cursor", "String", false, true);
writer.write("): ["+name+"]\r\n"); writer.write("): [" + name + "]\r\n");
writer.write("}\r\n");
writer.write("\r\n");
} }
private void param(BufferedWriter writer, String name, String type, boolean list, boolean line) throws IOException { private void param(Writer writer, String name, String type, boolean list, boolean line) throws IOException {
if (line) if (line)
writer.write("\r\n "); writer.write("\r\n ");
writer.write(name); writer.write(name);
writer.write(": "); writer.write(": ");
if (list) if (list)
writer.write("["); writer.write("[");
writer.write(type); writer.write(type);
if (list) if (list)
writer.write("]"); writer.write("]");
} }
private void generateConnectionAccess(BufferedWriter writer, List<SearchParameter> parameters, String name) throws IOException { private void generateConnectionAccess(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
writer.write("type "+name+"ConnectionType {\r\n"); writer.write("type " + name + "ConnectionType {\r\n");
writer.write(" "+name+"Conection("); writer.write(" ");
param(writer, "_filter", "String", false, false); generateConnectionAccessQuery(writer, parameters, name);
for (SearchParameter sp : parameters)
param(writer, sp.getName().replace("-", "_"), getGqlname(sp.getType().toCode()), true, true);
param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): "+name+"Connection\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type "+name+"Connection {\r\n"); writer.write("type " + name + "Connection {\r\n");
writer.write(" count: Int\r\n"); writer.write(" count: Int\r\n");
writer.write(" offset: Int\r\n"); writer.write(" offset: Int\r\n");
writer.write(" pagesize: Int\r\n"); writer.write(" pagesize: Int\r\n");
@ -224,32 +234,42 @@ public class GraphQLSchemaGenerator {
writer.write(" previous: ID\r\n"); writer.write(" previous: ID\r\n");
writer.write(" next: ID\r\n"); writer.write(" next: ID\r\n");
writer.write(" last: ID\r\n"); writer.write(" last: ID\r\n");
writer.write(" edges: ["+name+"Edge]\r\n"); writer.write(" edges: [" + name + "Edge]\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("type "+name+"Edge {\r\n"); writer.write("type " + name + "Edge {\r\n");
writer.write(" mode: String\r\n"); writer.write(" mode: String\r\n");
writer.write(" score: Float\r\n"); writer.write(" score: Float\r\n");
writer.write(" resource: "+name+"\r\n"); writer.write(" resource: " + name + "\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
} }
public void generateConnectionAccessQuery(Writer writer, List<SearchParameter> parameters, String name) throws IOException {
private void generateIdAccess(BufferedWriter writer, String name) throws IOException { writer.write(name + "Conection(");
writer.write("type "+name+"ReadType {\r\n"); param(writer, "_filter", "String", false, false);
writer.write(" "+name+"(id: ID!): "+name+"\r\n"); for (SearchParameter sp : parameters)
writer.write("}\r\n"); param(writer, sp.getName().replace("-", "_"), getGqlname(requireNonNull(sp.getType().toCode())), true, true);
writer.write("\r\n"); param(writer, "_sort", "String", false, true);
param(writer, "_count", "Int", false, true);
param(writer, "_cursor", "String", false, true);
writer.write("): " + name + "Connection\r\n");
} }
private void generateElementBase(BufferedWriter writer) throws IOException { private void generateIdAccess(Writer writer, String name) throws IOException {
writer.write("type " + name + "ReadType {\r\n");
writer.write(" " + name + "(id: ID!): " + name + "\r\n");
writer.write("}\r\n");
writer.write("\r\n");
}
private void generateElementBase(Writer writer) throws IOException {
writer.write("type ElementBase {\r\n"); writer.write("type ElementBase {\r\n");
writer.write(" id: ID\r\n"); writer.write(" id: ID\r\n");
writer.write(" extension: [Extension]\r\n"); writer.write(" extension: [Extension]\r\n");
writer.write("}\r\n"); writer.write("}\r\n");
writer.write("\r\n"); writer.write("\r\n");
writer.write("input ElementBaseInput {\r\n"); writer.write("input ElementBaseInput {\r\n");
writer.write(" id : ID\r\n"); writer.write(" id : ID\r\n");
writer.write(" extension: [ExtensionInput]\r\n"); writer.write(" extension: [ExtensionInput]\r\n");
@ -257,18 +277,18 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateType(BufferedWriter writer, StructureDefinition sd) throws IOException { private void generateType(Map<String, String> existingTypeNames, Writer writer, StructureDefinition sd) throws IOException {
if (sd.getAbstract()) if (sd.getAbstract())
return; return;
List<StringBuilder> list = new ArrayList<StringBuilder>(); List<StringBuilder> list = new ArrayList<>();
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
list.add(b); list.add(b);
b.append("type "); b.append("type ");
b.append(sd.getName()); b.append(sd.getName());
b.append(" {\r\n"); b.append(" {\r\n");
ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
generateProperties(list, b, sd.getName(), sd, ed, "type", ""); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "type", "");
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
@ -281,7 +301,7 @@ public class GraphQLSchemaGenerator {
b.append(sd.getName()); b.append(sd.getName());
b.append("Input {\r\n"); b.append("Input {\r\n");
ed = sd.getSnapshot().getElementFirstRep(); ed = sd.getSnapshot().getElementFirstRep();
generateProperties(list, b, sd.getName(), sd, ed, "input", "Input"); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "input", "Input");
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
@ -289,22 +309,22 @@ public class GraphQLSchemaGenerator {
writer.write(bs.toString()); writer.write(bs.toString());
} }
private void generateProperties(List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition ed, String mode, String suffix) throws IOException { private void generateProperties(Map<String, String> existingTypeNames, List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition ed, String mode, String suffix) throws IOException {
List<ElementDefinition> children = profileUtilities.getChildList(sd, ed); List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
for (ElementDefinition child : children) { for (ElementDefinition child : children) {
if (child.hasContentReference()) { if (child.hasContentReference()) {
ElementDefinition ref = resolveContentReference(sd, child.getContentReference()); ElementDefinition ref = resolveContentReference(sd, child.getContentReference());
generateProperty(list, b, typeName, sd, child, ref.getType().get(0), false, ref, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, ref.getType().get(0), false, ref, mode, suffix);
} else if (child.getType().size() == 1) { } else if (child.getType().size() == 1) {
generateProperty(list, b, typeName, sd, child, child.getType().get(0), false, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, child.getType().get(0), false, null, mode, suffix);
} else { } else {
boolean ref = false; boolean ref = false;
for (TypeRefComponent t : child.getType()) { for (TypeRefComponent t : child.getType()) {
if (!t.hasTarget()) if (!t.hasTarget())
generateProperty(list, b, typeName, sd, child, t, true, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, t, true, null, mode, suffix);
else if (!ref) { else if (!ref) {
ref = true; ref = true;
generateProperty(list, b, typeName, sd, child, t, true, null, mode, suffix); generateProperty(existingTypeNames, list, b, typeName, sd, child, t, true, null, mode, suffix);
} }
} }
} }
@ -317,12 +337,12 @@ public class GraphQLSchemaGenerator {
if (id.equals(ed.getId())) if (id.equals(ed.getId()))
return ed; return ed;
} }
throw new Error("Unable to find "+id); throw new Error("Unable to find " + id);
} }
private void generateProperty(List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition child, TypeRefComponent typeDetails, boolean suffix, ElementDefinition cr, String mode, String suffixS) throws IOException { private void generateProperty(Map<String, String> existingTypeNames, List<StringBuilder> list, StringBuilder b, String typeName, StructureDefinition sd, ElementDefinition child, TypeRefComponent typeDetails, boolean suffix, ElementDefinition cr, String mode, String suffixS) throws IOException {
if (isPrimitive(typeDetails)) { if (isPrimitive(typeDetails)) {
String n = getGqlname(typeDetails.getWorkingCode()); String n = getGqlname(typeDetails.getWorkingCode());
b.append(" "); b.append(" ");
b.append(tail(child.getPath(), suffix)); b.append(tail(child.getPath(), suffix));
if (suffix) if (suffix)
@ -334,15 +354,15 @@ public class GraphQLSchemaGenerator {
b.append(tail(child.getPath(), suffix)); b.append(tail(child.getPath(), suffix));
if (suffix) if (suffix)
b.append(Utilities.capitalize(typeDetails.getWorkingCode())); b.append(Utilities.capitalize(typeDetails.getWorkingCode()));
if (!child.getMax().equals("1")) { if (!child.getMax().equals("1")) {
b.append(": [ElementBase"); b.append(": [ElementBase");
b.append(suffixS); b.append(suffixS);
b.append("]\r\n"); b.append("]\r\n");
} else { } else {
b.append(": ElementBase"); b.append(": ElementBase");
b.append(suffixS); b.append(suffixS);
b.append("\r\n"); b.append("\r\n");
} }
} else } else
b.append("\r\n"); b.append("\r\n");
} else { } else {
@ -355,11 +375,11 @@ public class GraphQLSchemaGenerator {
b.append("["); b.append("[");
String type = typeDetails.getWorkingCode(); String type = typeDetails.getWorkingCode();
if (cr != null) if (cr != null)
b.append(generateInnerType(list, sd, typeName, cr, mode, suffixS)); b.append(generateInnerType(existingTypeNames, list, sd, typeName, cr, mode, suffixS));
else if (Utilities.existsInList(type, "Element", "BackboneElement")) else if (Utilities.existsInList(type, "Element", "BackboneElement"))
b.append(generateInnerType(list, sd, typeName, child, mode, suffixS)); b.append(generateInnerType(existingTypeNames, list, sd, typeName, child, mode, suffixS));
else else
b.append(type+suffixS); b.append(type).append(suffixS);
if (!child.getMax().equals("1")) if (!child.getMax().equals("1"))
b.append("]"); b.append("]");
if (child.getMin() != 0 && !suffix) if (child.getMin() != 0 && !suffix)
@ -368,12 +388,15 @@ public class GraphQLSchemaGenerator {
} }
} }
private String generateInnerType(List<StringBuilder> list, StructureDefinition sd, String name, ElementDefinition child, String mode, String suffix) throws IOException { private String generateInnerType(Map<String, String> existingTypeNames, List<StringBuilder> list, StructureDefinition sd, String name, ElementDefinition child, String mode, String suffix) throws IOException {
if (child.hasUserData(INNER_TYPE_NAME+"."+mode)) String key = child.getName() + "." + mode;
return child.getUserString(INNER_TYPE_NAME+"."+mode); if (existingTypeNames.containsKey(key)) {
return existingTypeNames.get(key);
String typeName = name+Utilities.capitalize(tail(child.getPath(), false)); }
child.setUserData(INNER_TYPE_NAME+"."+mode, typeName);
String typeName = name + Utilities.capitalize(tail(child.getPath(), false)) + suffix;
existingTypeNames.put(key, typeName + suffix);
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
list.add(b); list.add(b);
b.append(mode); b.append(mode);
@ -381,16 +404,16 @@ public class GraphQLSchemaGenerator {
b.append(typeName); b.append(typeName);
b.append(suffix); b.append(suffix);
b.append(" {\r\n"); b.append(" {\r\n");
generateProperties(list, b, typeName, sd, child, mode, suffix); generateProperties(existingTypeNames, list, b, typeName, sd, child, mode, suffix);
b.append("}"); b.append("}");
b.append("\r\n"); b.append("\r\n");
b.append("\r\n"); b.append("\r\n");
return typeName+suffix; return typeName + suffix;
} }
private String tail(String path, boolean suffix) { private String tail(String path, boolean suffix) {
if (suffix) if (suffix)
path = path.substring(0, path.length()-3); path = path.substring(0, path.length() - 3);
int i = path.lastIndexOf("."); int i = path.lastIndexOf(".");
return i < 0 ? path : path.substring(i + 1); return i < 0 ? path : path.substring(i + 1);
} }
@ -404,20 +427,19 @@ public class GraphQLSchemaGenerator {
} }
private List<String> sorted(Set<String> keys) { private List<String> sorted(Set<String> keys) {
List<String> sl = new ArrayList<>(); List<String> sl = new ArrayList<>(keys);
sl.addAll(keys);
Collections.sort(sl); Collections.sort(sl);
return sl; return sl;
} }
private void generatePrimitive(BufferedWriter writer, StructureDefinition sd) throws IOException, FHIRException { private void generatePrimitive(Writer writer, StructureDefinition sd) throws IOException, FHIRException {
String gqlName = getGqlname(sd.getName()); String gqlName = getGqlname(sd.getName());
if (gqlName.equals(sd.getName())) { if (gqlName.equals(sd.getName())) {
writer.write("scalar "); writer.write("scalar ");
writer.write(sd.getName()); writer.write(sd.getName());
writer.write(" # JSON Format: "); writer.write(" # JSON Format: ");
writer.write(getJsonFormat(sd)); writer.write(getJsonFormat(sd));
} else { } else {
writer.write("# Type "); writer.write("# Type ");
writer.write(sd.getName()); writer.write(sd.getName());
writer.write(": use GraphQL Scalar type "); writer.write(": use GraphQL Scalar type ");
@ -426,17 +448,17 @@ public class GraphQLSchemaGenerator {
writer.write("\r\n"); writer.write("\r\n");
} }
private void generateSearchParamType(BufferedWriter writer, String name) throws IOException, FHIRException { private void generateSearchParamType(Writer writer, String name) throws IOException, FHIRException {
String gqlName = getGqlname(name); String gqlName = getGqlname(name);
if (gqlName.equals("date")) { if (gqlName.equals("date")) {
writer.write("# Search Param "); writer.write("# Search Param ");
writer.write(name); writer.write(name);
writer.write(": already defined as Primitive with JSON Format: string "); writer.write(": already defined as Primitive with JSON Format: string ");
} else if (gqlName.equals(name)) { } else if (gqlName.equals(name)) {
writer.write("scalar "); writer.write("scalar ");
writer.write(name); writer.write(name);
writer.write(" # JSON Format: string"); writer.write(" # JSON Format: string");
} else { } else {
writer.write("# Search Param "); writer.write("# Search Param ");
writer.write(name); writer.write(name);
writer.write(": use GraphQL Scalar type "); writer.write(": use GraphQL Scalar type ");
@ -444,10 +466,10 @@ public class GraphQLSchemaGenerator {
} }
writer.write("\r\n"); writer.write("\r\n");
} }
private String getJsonFormat(StructureDefinition sd) throws FHIRException { private String getJsonFormat(StructureDefinition sd) throws FHIRException {
for (ElementDefinition ed : sd.getSnapshot().getElement()) { for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (!ed.getType().isEmpty() && ed.getType().get(0).getCodeElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type")) if (!ed.getType().isEmpty() && ed.getType().get(0).getCodeElement().hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type"))
return ed.getType().get(0).getCodeElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type"); return ed.getType().get(0).getCodeElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/structuredefinition-json-type");
} }
// all primitives but JSON_NUMBER_TYPES are represented as JSON strings // all primitives but JSON_NUMBER_TYPES are represented as JSON strings
@ -466,7 +488,9 @@ public class GraphQLSchemaGenerator {
if (name.equals("boolean")) if (name.equals("boolean"))
return "Boolean"; return "Boolean";
if (name.equals("id")) if (name.equals("id"))
return "ID"; return "ID";
return name; return name;
} }
public enum FHIROperationType {READ, SEARCH, CREATE, UPDATE, DELETE}
} }