diff --git a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java
index 62d60c3696c..9fa959270f0 100644
--- a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java
+++ b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/client/GenericClientDstu3IT.java
@@ -64,7 +64,7 @@ public class GenericClientDstu3IT {
}
private String expectedUserAgent() {
- return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.DSTU3.getFhirVersionString() + "/DSTU3; okhttp/3.4.1)";
+ return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.DSTU3.getFhirVersionString() + "/DSTU3; okhttp/3.8.1)";
}
@@ -94,6 +94,7 @@ public class GenericClientDstu3IT {
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_XML + "; charset=UTF-8"), respString))
+ .message("")
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
@@ -131,7 +132,6 @@ public class GenericClientDstu3IT {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
- //@formatter:off
client
.search()
.forResource(Patient.class)
@@ -141,7 +141,6 @@ public class GenericClientDstu3IT {
.and(Patient.ORGANIZATION.hasId((String)null))
.returnBundle(Bundle.class)
.execute();
- //@formatter:on
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).url().toString());
idx++;
@@ -166,6 +165,7 @@ public class GenericClientDstu3IT {
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"), respString))
+ .message("")
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
@@ -197,6 +197,7 @@ public class GenericClientDstu3IT {
.protocol(myProtocol)
.code(200)
.body(body)
+ .message("")
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
@@ -242,6 +243,7 @@ public class GenericClientDstu3IT {
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"), respString))
.headers(Headers.of(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3"))
+ .message("")
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
@@ -267,6 +269,7 @@ public class GenericClientDstu3IT {
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON + "; charset=UTF-8"), respString))
.headers(Headers.of(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3"))
+ .message("")
.build();
return capt;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index ee8414c9229..106d9c6589e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.context;
+import java.io.IOException;
import java.lang.reflect.Method;
/*
@@ -320,11 +321,11 @@ public class FhirContext {
Map> nameToType = myVersionToNameToResourceType.get(theVersion);
if (nameToType == null) {
- nameToType = new HashMap>();
- Map, BaseRuntimeElementDefinition>> existing = Collections.emptyMap();
+ nameToType = new HashMap<>();
+ Map, BaseRuntimeElementDefinition>> existing = new HashMap<>();
ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion, existing);
- Map>> newVersionToNameToResourceType = new HashMap>>();
+ Map>> newVersionToNameToResourceType = new HashMap<>();
newVersionToNameToResourceType.putAll(myVersionToNameToResourceType);
newVersionToNameToResourceType.put(theVersion, nameToType);
myVersionToNameToResourceType = newVersionToNameToResourceType;
@@ -905,4 +906,33 @@ public class FhirContext {
return retVal;
}
+ /**
+ * Returns an unmodifiable set containing all resource names known to this
+ * context
+ */
+ public Set getResourceNames() {
+ Set resourceNames= new HashSet<>();
+
+ if (myNameToResourceDefinition.isEmpty()) {
+ Properties props = new Properties();
+ try {
+ props.load(myVersion.getFhirVersionPropertiesFile());
+ } catch (IOException theE) {
+ throw new ConfigurationException("Failed to load version properties file");
+ }
+ Enumeration> propNames = props.propertyNames();
+ while (propNames.hasMoreElements()){
+ String next = (String) propNames.nextElement();
+ if (next.startsWith("resource.")) {
+ resourceNames.add(next.substring("resource.".length()).trim());
+ }
+ }
+ }
+
+ for (RuntimeResourceDefinition next : myNameToResourceDefinition.values()) {
+ resourceNames.add(next.getName());
+ }
+
+ return Collections.unmodifiableSet(resourceNames);
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQL.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQL.java
new file mode 100644
index 00000000000..6fdaf2333b9
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQL.java
@@ -0,0 +1,15 @@
+package ca.uhn.fhir.rest.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A method annotated with this annotation will be treated as a GraphQL implementation
+ * method
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value= ElementType.METHOD)
+public @interface GraphQL {
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQLQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQLQuery.java
new file mode 100644
index 00000000000..cd75f82c93b
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/GraphQLQuery.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.rest.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should be placed on the parameter of a
+ * {@link GraphQL @GraphQL} annotated method. The given
+ * parameter will be populated with the specific graphQL
+ * query being requested.
+ *
+ *
+ * This parameter should be of type {@link String}
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface GraphQLQuery {
+ // ignore
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
index 6df74d36b0b..b68de733a3c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Operation.java
@@ -79,5 +79,5 @@ public @interface Operation {
* bundle type to set in the bundle.
*/
BundleTypeEnum bundleType() default BundleTypeEnum.COLLECTION;
-
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 9c2a98d0708..4d067c33677 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -171,6 +171,7 @@ public class Constants {
public static final String URL_TOKEN_HISTORY = "_history";
public static final String URL_TOKEN_METADATA = "metadata";
public static final String OO_INFOSTATUS_PROCESSING = "processing";
+ public static final String PARAM_GRAPHQL_QUERY = "query";
static {
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestOperationTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestOperationTypeEnum.java
index 7ab59b2a53e..3953d2a64d1 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestOperationTypeEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/RestOperationTypeEnum.java
@@ -37,6 +37,14 @@ public enum RestOperationTypeEnum {
GET_PAGE("get-page"),
+ /**
+ *
+ * Use this value with caution, this may
+ * change as the GraphQL interface matures
+ *
+ */
+ GRAPHQL_REQUEST("graphql-request"),
+
/**
* E.g. $everything, $validate, etc.
*/
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
index 93b9717ef89..b345a6d02b2 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java
@@ -1,10 +1,10 @@
package ca.uhn.fhir.rest.gclient;
-import java.util.List;
-
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import ca.uhn.fhir.rest.api.SummaryEnum;
+import java.util.List;
/*
* #%L
@@ -45,6 +45,8 @@ public interface IClientExecutable, Y> {
*/
T elementsSubset(String... theElements);
+ T encoded(EncodingEnum theEncoding);
+
T encodedJson();
T encodedXml();
@@ -54,8 +56,6 @@ public interface IClientExecutable, Y> {
*/
Y execute();
- T prettyPrint();
-
/**
* Explicitly specify a custom structure type to attempt to use when parsing the response. This
* is useful for invocations where the response is a Bundle/Parameters containing nested resources,
@@ -77,6 +77,8 @@ public interface IClientExecutable, Y> {
*/
T preferResponseTypes(List> theTypes);
+ T prettyPrint();
+
/**
* Request that the server modify the response using the _summary param
*/
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
index 0aa0668e8e3..c82d329a18e 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
@@ -400,6 +400,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
+ @Override
+ public T encoded(EncodingEnum theEncoding) {
+ Validate.notNull(theEncoding, "theEncoding must not be null");
+ myParamEncoding = theEncoding;
+ return (T) this;
+ }
+
@SuppressWarnings("unchecked")
@Override
public T encodedXml() {
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index b7a45574dc4..e95587915f2 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -264,6 +264,12 @@
org.springframeworkspring-messaging
+
org.springframeworkspring-tx
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 0369481da78..90414d1b0f0 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -22,6 +22,9 @@ package ca.uhn.fhir.jpa.config;
import javax.annotation.Resource;
+import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
+import org.hl7.fhir.r4.utils.GraphQLEngine;
+import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
@@ -113,4 +116,9 @@ public class BaseConfig implements SchedulingConfigurer {
return new PropertySourcesPlaceholderConfigurer();
}
+ @Bean
+ public IGraphQLStorageServices jpaStorageServices() {
+ return new JpaStorageServices();
+ }
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
index 30816a5327a..3a70c9eabe1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
@@ -28,7 +28,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
-import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu2Interceptor;
+import ca.uhn.fhir.jpa.subscription.dstu2.RestHookSubscriptionDstu2Interceptor;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java
index 3c037e62968..d57864e9fb9 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2Config.java
@@ -31,7 +31,7 @@ import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
-import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu2;
+import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
@Configuration
@EnableWebSocket()
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java
index 733793e0452..d20b3d7e63b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDstu2DispatcherConfig.java
@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
-import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu2;
+import ca.uhn.fhir.jpa.subscription.dstu2.SubscriptionWebsocketHandlerDstu2;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
@Configuration
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
index 7e17c4f3d20..0c4ab5aac79 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
@@ -40,7 +40,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
-import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu3Interceptor;
+import ca.uhn.fhir.jpa.subscription.dstu3.RestHookSubscriptionDstu3Interceptor;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java
index 88656845d2d..e34817e7cfb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3Config.java
@@ -23,7 +23,6 @@ package ca.uhn.fhir.jpa.config.dstu3;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
@@ -33,8 +32,8 @@ import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
-import ca.uhn.fhir.jpa.interceptor.WebSocketSubscriptionDstu3Interceptor;
-import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu3;
+import ca.uhn.fhir.jpa.subscription.dstu3.WebSocketSubscriptionDstu3Interceptor;
+import ca.uhn.fhir.jpa.subscription.dstu3.SubscriptionWebsocketHandlerDstu3;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@Configuration
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java
index 038fad7bb1e..c92ecfc6fc8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/WebsocketDstu3DispatcherConfig.java
@@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
-import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu3;
+import ca.uhn.fhir.jpa.subscription.dstu3.SubscriptionWebsocketHandlerDstu3;
@Configuration
public class WebsocketDstu3DispatcherConfig {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
index 820de327f69..60df39dab23 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
@@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.config.r4;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
+import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
@@ -34,7 +35,7 @@ import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.r4.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.dao.r4.SearchParamRegistryR4;
-import ca.uhn.fhir.jpa.interceptor.r4.RestHookSubscriptionR4Interceptor;
+import ca.uhn.fhir.jpa.subscription.r4.RestHookSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.term.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcR4;
@@ -78,6 +79,12 @@ public class BaseR4Config extends BaseConfig {
return searchDao;
}
+ @Bean(name = "myGraphQLProvider")
+ @Lazy
+ public GraphQLProvider graphQLProvider() {
+ return new GraphQLProvider(fhirContextR4(), validationSupportChainR4(), jpaStorageServices());
+ }
+
@Bean(autowire = Autowire.BY_TYPE)
public SearchParamExtractorR4 searchParamExtractor() {
return new SearchParamExtractorR4();
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java
index fd5605e29ef..34446ed084f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/WebsocketR4Config.java
@@ -30,7 +30,7 @@ import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
-import ca.uhn.fhir.jpa.interceptor.r4.WebSocketSubscriptionR4Interceptor;
+import ca.uhn.fhir.jpa.subscription.r4.WebSocketSubscriptionR4Interceptor;
import ca.uhn.fhir.jpa.subscription.r4.SubscriptionWebsocketHandlerR4;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index 9ca8b013d98..dc0b30a4b6b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -495,8 +495,8 @@ public abstract class BaseHapiFhirDao implements IDao {
}
@SuppressWarnings("unchecked")
- private void findMissingSearchParams(ResourceTable theEntity, Set> activeSearchParams, RestSearchParameterTypeEnum type,
- Set paramCollection) {
+ private
-
- org.eclipse.jetty.websocket
- websocket-api
- test
-
-
- org.eclipse.jetty.websocket
- websocket-client
- test
- org.eclipse.jetty.websocketwebsocket-server
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IFhirVersionServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IFhirVersionServer.java
index 328697ffc0f..7fab0c51109 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IFhirVersionServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/IFhirVersionServer.java
@@ -35,5 +35,5 @@ public interface IFhirVersionServer {
IServerConformanceProvider extends IBaseResource> createServerConformanceProvider(RestfulServer theRestfulServer);
IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer);
-
+
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index 453d3d4b35b..003d45439a0 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -55,6 +55,7 @@ import ca.uhn.fhir.rest.server.method.*;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*;
+@SuppressWarnings("WeakerAccess")
public class RestfulServer extends HttpServlet implements IRestfulServer {
/**
@@ -67,17 +68,17 @@ public class RestfulServer extends HttpServlet implements IRestfulServer myInterceptors = new ArrayList<>();
+ private final List myPlainProviders = new ArrayList<>();
+ private final List myResourceProviders = new ArrayList<>();
private BundleInclusionRule myBundleInclusionRule = BundleInclusionRule.BASED_ON_INCLUDES;
private boolean myDefaultPrettyPrint = false;
private EncodingEnum myDefaultResponseEncoding = EncodingEnum.XML;
@@ -85,21 +86,19 @@ public class RestfulServer extends HttpServlet implements IRestfulServer myInterceptors = new ArrayList();
private IPagingProvider myPagingProvider;
- private final List myPlainProviders = new ArrayList();
private Lock myProviderRegistrationMutex = new ReentrantLock();
- private Map myResourceNameToBinding = new HashMap();
- private final List myResourceProviders = new ArrayList();
+ private Map myResourceNameToBinding = new HashMap<>();
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
private ResourceBinding myServerBinding = new ResourceBinding();
+ private ResourceBinding myGlobalBinding = new ResourceBinding();
private BaseMethodBinding> myServerConformanceMethod;
private Object myServerConformanceProvider;
private String myServerName = "HAPI FHIR Server";
/** This is configurable but by default we just use HAPI version */
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
- private Map myTypeToProvider = new HashMap();
+ private Map myTypeToProvider = new HashMap<>();
private boolean myUncompressIncomingContents = true;
private boolean myUseBrowserFriendlyContentTypes;
@@ -120,6 +119,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
+ }
+
private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) {
if (response != null && response.getId() != null) {
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName);
@@ -131,7 +134,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
* Use caution if overriding this method: it is recommended to call super.addHeadersToResponse to avoid
- * inadvertantly disabling functionality.
+ * inadvertently disabling functionality.
*
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@@ -210,6 +213,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer determineResourceMethod(RequestDetails requestDetails, String requestPath) {
RequestTypeEnum requestType = requestDetails.getRequestType();
@@ -231,11 +238,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer_format URL parameter, or with an Accept header
@@ -360,11 +401,39 @@ public class RestfulServer extends HttpServlet implements IRestfulServer_format URL parameter, or with an Accept header in
+ * the request. The default is {@link EncodingEnum#XML}.
+ *
+ * Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
+ * that the
+ *
+ */
+ public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
+ Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
+ myDefaultResponseEncoding = theDefaultResponseEncoding;
+ }
+
@Override
public ETagSupportEnum getETagSupport() {
return myETagSupport;
}
+ /**
+ * Sets (enables/disables) the server support for ETags. Must not be null. Default is
+ * {@link #DEFAULT_ETAG_SUPPORT}
+ *
+ * @param theETagSupport
+ * The ETag support mode
+ */
+ public void setETagSupport(ETagSupportEnum theETagSupport) {
+ if (theETagSupport == null) {
+ throw new NullPointerException("theETagSupport can not be null");
+ }
+ myETagSupport = theETagSupport;
+ }
+
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to
@@ -379,10 +448,19 @@ public class RestfulServer extends HttpServlet implements IRestfulServer theList) {
+ myInterceptors.clear();
+ if (theList != null) {
+ myInterceptors.addAll(theList);
+ }
+ }
+
@Override
public IPagingProvider getPagingProvider() {
return myPagingProvider;
}
+ /**
+ * Sets the paging provider to use, or null to use no paging (which is the default)
+ */
+ public void setPagingProvider(IPagingProvider thePagingProvider) {
+ myPagingProvider = thePagingProvider;
+ }
+
/**
* Provides the non-resource specific providers which implement method calls on this server
*
@@ -405,6 +516,27 @@ public class RestfulServer extends HttpServlet implements IRestfulServer theProviders) {
+ myPlainProviders.clear();
+ if (theProviders != null) {
+ myPlainProviders.addAll(theProviders);
+ }
+ }
+
+ /**
+ * Sets the non-resource specific providers which implement method calls on this server.
+ *
+ * @see #setResourceProviders(Collection)
+ */
+ public void setPlainProviders(Object... theProv) {
+ setPlainProviders(Arrays.asList(theProv));
+ }
+
/**
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation
@@ -432,6 +564,26 @@ public class RestfulServer extends HttpServlet implements IRestfulServer theResourceProviders) {
+ myResourceProviders.clear();
+ if (theResourceProviders != null) {
+ myResourceProviders.addAll(theResourceProviders);
+ }
+ }
+
+ /**
+ * Sets the resource providers for this server
+ */
+ public void setResourceProviders(IResourceProvider... theResourceProviders) {
+ myResourceProviders.clear();
+ if (theResourceProviders != null) {
+ myResourceProviders.addAll(Arrays.asList(theResourceProviders));
+ }
+ }
+
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
@@ -440,6 +592,15 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
+ * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
+ * changed, or set to null if you do not wish to export a conformance
+ * statement.
+ *
+ * Note that this method can only be called before the server is initialized.
+ *
+ * @throws IllegalStateException
+ * Note that this method can only be called prior to {@link #init() initialization} and will throw an
+ * {@link IllegalStateException} if called after that.
+ */
+ public void setServerConformanceProvider(Object theServerConformanceProvider) {
+ if (myStarted) {
+ throw new IllegalStateException("Server is already started");
+ }
+
+ // call the setRestfulServer() method to point the Conformance
+ // Provider to this server instance. This is done to avoid
+ // passing the server into the constructor. Having that sort
+ // of cross linkage causes reference cycles in Spring wiring
+ try {
+ Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] { RestfulServer.class });
+ if (setRestfulServer != null) {
+ setRestfulServer.invoke(theServerConformanceProvider, new Object[] { this });
+ }
+ } catch (Exception e) {
+ ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
+ }
+ myServerConformanceProvider = theServerConformanceProvider;
+ }
+
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
@@ -484,6 +679,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServer(theRequest.getParameterMap());
+ params = new HashMap<>(theRequest.getParameterMap());
}
requestDetails.setParameters(params);
@@ -560,7 +772,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer 0 && requestPath.charAt(0) == '/') {
@@ -578,7 +790,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer= 0; i--) {
IServerInterceptor next = getInterceptors().get(i);
@@ -665,25 +877,13 @@ public class RestfulServer extends HttpServlet implements IRestfulServer= 0; i--) {
- IServerInterceptor next = getInterceptors().get(i);
- if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
- ourLog.debug("Interceptor {} returned false, not continuing processing");
- return;
- }
- }
-
- writeExceptionToResponse(theResponse, e);
-
} catch (Throwable e) {
/*
* We have caught an exception during request processing. This might be because a handling method threw
* something they wanted to throw (e.g. UnprocessableEntityException because the request
* had business requirement problems) or it could be due to bugs (e.g. NullPointerException).
- *
+ *
* First we let the interceptors have a crack at converting the exception into something HAPI can use
* (BaseServerResponseException)
*/
@@ -807,7 +1007,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer[] paramTypes = m.getParameterTypes();
+ Object[] params = new Object[paramTypes.length];
+
+ int index = 0;
+ for (Class> nextParamType : paramTypes) {
+
+ if (RestfulServer.class.equals(nextParamType) || IRestfulServerDefaults.class.equals(nextParamType)) {
+ params[index] = this;
+ }
+
+ index++;
+ }
+
+ try {
+ m.invoke(theProvider, params);
+ } catch (Exception e) {
+ ourLog.error("Exception occurred in " + theMethodDescription + " method '" + m.getName() + "'", e);
+ }
+ }
+
private void invokeInitialize(Object theProvider) {
invokeInitialize(theProvider, theProvider.getClass());
}
@@ -850,14 +1065,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer
* The default is false
*
- *
+ *
* @return Returns the default pretty print setting
*/
@Override
@@ -882,6 +1090,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServerAccept header in the request, or a _pretty
+ * parameter in the request URL.
+ *
+ * The default is false
+ *
+ *
+ * @param theDefaultPrettyPrint
+ * The default pretty print setting
+ */
+ public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
+ myDefaultPrettyPrint = theDefaultPrettyPrint;
+ }
+
/**
* If set to true (the default is true) this server will not
* use the parsed request parameters (URL parameters and HTTP POST form contents) but
@@ -896,6 +1119,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServertrue (the default is true) this server will not
+ * use the parsed request parameters (URL parameters and HTTP POST form contents) but
+ * will instead parse these values manually from the request URL and request body.
+ *
+ * This is useful because many servlet containers (e.g. Tomcat, Glassfish) will use
+ * ISO-8859-1 encoding to parse escaped URL characters instead of using UTF-8
+ * as is specified by FHIR.
+ *
+ */
+ public void setIgnoreServerParsedRequestParameters(boolean theIgnoreServerParsedRequestParameters) {
+ myIgnoreServerParsedRequestParameters = theIgnoreServerParsedRequestParameters;
+ }
+
/**
* Should the server attempt to decompress incoming request contents (default is true). Typically this
* should be set to true unless the server has other configuration to
@@ -905,6 +1142,15 @@ public class RestfulServer extends HttpServlet implements IRestfulServertrue). Typically this
+ * should be set to true unless the server has other configuration to
+ * deal with decompressing request bodies (e.g. a filter applied to the whole server).
+ */
+ public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
+ myUncompressIncomingContents = theUncompressIncomingContents;
+ }
+
/**
* @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
* instead as an interceptor on your server and it will provide more useful syntax
@@ -916,6 +1162,16 @@ public class RestfulServer extends HttpServlet implements IRestfulServer providerList = new ArrayList(1);
+ Collection providerList = new ArrayList<>(1);
providerList.add(provider);
registerProviders(providerList);
}
@@ -1003,7 +1259,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerAccept header in the request, or a _pretty
- * parameter in the request URL.
- *
- * The default is false
- *
- *
- * @param theDefaultPrettyPrint
- * The default pretty print setting
- */
- public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
- myDefaultPrettyPrint = theDefaultPrettyPrint;
- }
-
- /**
- * Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
- * the _format URL parameter, or with an Accept header in
- * the request. The default is {@link EncodingEnum#XML}.
- *
- * Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
- * that the
- *
- */
- public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
- Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
- myDefaultResponseEncoding = theDefaultResponseEncoding;
- }
-
- /**
- * Sets (enables/disables) the server support for ETags. Must not be null. Default is
- * {@link #DEFAULT_ETAG_SUPPORT}
- *
- * @param theETagSupport
- * The ETag support mode
- */
- public void setETagSupport(ETagSupportEnum theETagSupport) {
- if (theETagSupport == null) {
- throw new NullPointerException("theETagSupport can not be null");
- }
- myETagSupport = theETagSupport;
- }
-
- public void setFhirContext(FhirContext theFhirContext) {
- Validate.notNull(theFhirContext, "FhirContext must not be null");
- myFhirContext = theFhirContext;
- }
-
- /**
- * If set to true (the default is true) this server will not
- * use the parsed request parameters (URL parameters and HTTP POST form contents) but
- * will instead parse these values manually from the request URL and request body.
- *
- * This is useful because many servlet containers (e.g. Tomcat, Glassfish) will use
- * ISO-8859-1 encoding to parse escaped URL characters instead of using UTF-8
- * as is specified by FHIR.
- *
- */
- public void setIgnoreServerParsedRequestParameters(boolean theIgnoreServerParsedRequestParameters) {
- myIgnoreServerParsedRequestParameters = theIgnoreServerParsedRequestParameters;
- }
-
- public void setImplementationDescription(String theImplementationDescription) {
- myImplementationDescription = theImplementationDescription;
- }
-
- /**
- * Sets (or clears) the list of interceptors
- *
- * @param theList
- * The list of interceptors (may be null)
- */
- public void setInterceptors(IServerInterceptor... theList) {
- myInterceptors.clear();
- if (theList != null) {
- myInterceptors.addAll(Arrays.asList(theList));
- }
- }
-
- /**
- * Sets (or clears) the list of interceptors
- *
- * @param theList
- * The list of interceptors (may be null)
- */
- public void setInterceptors(List theList) {
- myInterceptors.clear();
- if (theList != null) {
- myInterceptors.addAll(theList);
- }
- }
-
- /**
- * Sets the paging provider to use, or null to use no paging (which is the default)
- */
- public void setPagingProvider(IPagingProvider thePagingProvider) {
- myPagingProvider = thePagingProvider;
- }
-
- /**
- * Sets the non-resource specific providers which implement method calls on this server.
- *
- * @see #setResourceProviders(Collection)
- */
- public void setPlainProviders(Collection theProviders) {
- myPlainProviders.clear();
- if (theProviders != null) {
- myPlainProviders.addAll(theProviders);
- }
- }
-
- /**
- * Sets the non-resource specific providers which implement method calls on this server.
- *
- * @see #setResourceProviders(Collection)
- */
- public void setPlainProviders(Object... theProv) {
- setPlainProviders(Arrays.asList(theProv));
- }
-
/**
* Sets the non-resource specific providers which implement method calls on this server
*
@@ -1371,104 +1480,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer theResourceProviders) {
- myResourceProviders.clear();
- if (theResourceProviders != null) {
- myResourceProviders.addAll(theResourceProviders);
- }
- }
-
- /**
- * Sets the resource providers for this server
- */
- public void setResourceProviders(IResourceProvider... theResourceProviders) {
- myResourceProviders.clear();
- if (theResourceProviders != null) {
- myResourceProviders.addAll(Arrays.asList(theResourceProviders));
- }
- }
-
- /**
- * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
- * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
- */
- public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
- Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
- myServerAddressStrategy = theServerAddressStrategy;
- }
-
- /**
- * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
- * (metadata) statement.
- *
- * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
- * changed, or set to null if you do not wish to export a conformance
- * statement.
- *
- * Note that this method can only be called before the server is initialized.
- *
- * @throws IllegalStateException
- * Note that this method can only be called prior to {@link #init() initialization} and will throw an
- * {@link IllegalStateException} if called after that.
- */
- public void setServerConformanceProvider(Object theServerConformanceProvider) {
- if (myStarted) {
- throw new IllegalStateException("Server is already started");
- }
-
- // call the setRestfulServer() method to point the Conformance
- // Provider to this server instance. This is done to avoid
- // passing the server into the constructor. Having that sort
- // of cross linkage causes reference cycles in Spring wiring
- try {
- Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] { RestfulServer.class });
- if (setRestfulServer != null) {
- setRestfulServer.invoke(theServerConformanceProvider, new Object[] { this });
- }
- } catch (Exception e) {
- ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
- }
- myServerConformanceProvider = theServerConformanceProvider;
- }
-
- /**
- * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
- * but can be helpful to set with something appropriate.
- */
- public void setServerName(String theServerName) {
- myServerName = theServerName;
- }
-
- /**
- * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
- * but can be helpful to set with something appropriate.
- */
- public void setServerVersion(String theServerVersion) {
- myServerVersion = theServerVersion;
- }
-
- /**
- * Should the server attempt to decompress incoming request contents (default is true). Typically this
- * should be set to true unless the server has other configuration to
- * deal with decompressing request bodies (e.g. a filter applied to the whole server).
- */
- public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
- myUncompressIncomingContents = theUncompressIncomingContents;
- }
-
- /**
- * @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
- * instead as an interceptor on your server and it will provide more useful syntax
- * highlighting. Deprocated in 1.4
- */
- @Deprecated
- public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
- myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
- }
-
public void unregisterInterceptor(IServerInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
@@ -1476,7 +1487,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
- }
-
+// /**
+// * Returns the read method binding for the given resource type, or
+// * returns null if not
+// * @param theResourceType The resource type, e.g. "Patient"
+// * @return The read method binding, or null
+// */
+// public ReadMethodBinding findReadMethodBinding(String theResourceType) {
+// ReadMethodBinding retVal = null;
+//
+// ResourceBinding type = myResourceNameToBinding.get(theResourceType);
+// if (type != null) {
+// for (BaseMethodBinding> next : type.getMethodBindings()) {
+// if (next instanceof ReadMethodBinding) {
+// retVal = (ReadMethodBinding) next;
+// }
+// }
+// }
+//
+// return retVal;
+// }
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index d2632cd729c..a8e7e94a0ed 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -543,7 +543,7 @@ public class RestfulServerUtils {
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set theSummaryMode, int theStausCode, String theStatusMessage,
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType theOperationResourceLastUpdated)
throws IOException {
- IRestfulResponse restUtil = theRequestDetails.getResponse();
+ IRestfulResponse response = theRequestDetails.getResponse();
// Determine response encoding
ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, theServer.getDefaultResponseEncoding());
@@ -561,14 +561,14 @@ public class RestfulServerUtils {
if (theAddContentLocationHeader && fullId != null) {
if (theServer.getFhirContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
- restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
+ response.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
}
- restUtil.addHeader(Constants.HEADER_LOCATION, fullId.getValue());
+ response.addHeader(Constants.HEADER_LOCATION, fullId.getValue());
}
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
if (fullId != null && fullId.hasVersionIdPart()) {
- restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + fullId.getVersionIdPart() + '"');
+ response.addHeader(Constants.HEADER_ETAG, "W/\"" + fullId.getVersionIdPart() + '"');
}
}
@@ -582,9 +582,9 @@ public class RestfulServerUtils {
}
// Force binary resources to download - This is a security measure to prevent
// malicious images or HTML blocks being served up as content.
- restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
+ response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
- return restUtil.sendAttachmentResponse(bin, theStausCode, contentType);
+ return response.sendAttachmentResponse(bin, theStausCode, contentType);
}
// Ok, we're not serving a binary resource, so apply default encoding
@@ -615,7 +615,7 @@ public class RestfulServerUtils {
lastUpdated = extractLastUpdatedFromResource(theResource);
}
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
- restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
+ response.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
}
/*
@@ -631,7 +631,7 @@ public class RestfulServerUtils {
}
String charset = Constants.CHARSET_NAME_UTF8;
- Writer writer = restUtil.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip);
+ Writer writer = response.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip);
if (theResource == null) {
// No response is being returned
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
@@ -641,7 +641,7 @@ public class RestfulServerUtils {
parser.encodeResourceToWriter(theResource, writer);
}
//FIXME resource leak
- return restUtil.sendWriterResponse(theStausCode, contentType, charset, writer);
+ return response.sendWriterResponse(theStausCode, contentType, charset, writer);
}
public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
index a25835c90b3..64658656a1c 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
@@ -113,6 +113,17 @@ public abstract class BaseMethodBinding {
return parser;
}
+ protected Object[] createMethodParams(RequestDetails theRequest) {
+ Object[] params = new Object[getParameters().size()];
+ for (int i = 0; i < getParameters().size(); i++) {
+ IParameter param = getParameters().get(i);
+ if (param != null) {
+ params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
+ }
+ }
+ return params;
+ }
+
protected Object[] createParametersForServerRequest(RequestDetails theRequest) {
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
@@ -125,6 +136,13 @@ public abstract class BaseMethodBinding {
return params;
}
+ /**
+ * Subclasses may override to declare that they apply to all resource types
+ */
+ public boolean isGlobalMethod() {
+ return false;
+ }
+
public List> getAllowableParamAnnotations() {
return null;
}
@@ -345,16 +363,21 @@ public abstract class BaseMethodBinding {
Operation operation = theMethod.getAnnotation(Operation.class);
GetPage getPage = theMethod.getAnnotation(GetPage.class);
Patch patch = theMethod.getAnnotation(Patch.class);
+ GraphQL graphQL = theMethod.getAnnotation(GraphQL.class);
// ** if you add another annotation above, also add it to the next line:
- if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, addTags, deleteTags, transaction, operation, getPage, patch)) {
+ if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, addTags, deleteTags, transaction, operation, getPage, patch, graphQL)) {
return null;
}
if (getPage != null) {
return new PageMethodBinding(theContext, theMethod);
}
-
+
+ if (graphQL != null) {
+ return new GraphQLMethodBinding(theMethod, theContext, theProvider);
+ }
+
Class extends IBaseResource> returnType;
Class extends IBaseResource> returnTypeFromRp = null;
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
index e5c6a12896d..9fe6ce1ed6f 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
@@ -170,14 +170,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
}
public IBaseResource doInvokeServer(IRestfulServer> theServer, RequestDetails theRequest) {
- // Method params
- Object[] params = new Object[getParameters().size()];
- for (int i = 0; i < getParameters().size(); i++) {
- IParameter param = getParameters().get(i);
- if (param != null) {
- params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
- }
- }
+ Object[] params = createMethodParams(theRequest);
Object resultObj = invokeServer(theServer, theRequest, params);
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java
new file mode 100644
index 00000000000..86669f2ed80
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLMethodBinding.java
@@ -0,0 +1,74 @@
+package ca.uhn.fhir.rest.server.method;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.api.server.IRestfulServer;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.ParameterUtil;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.RestfulServerUtils;
+import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.lang.reflect.Method;
+
+public class GraphQLMethodBinding extends BaseMethodBinding {
+
+ private final Integer myIdParamIndex;
+
+ public GraphQLMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
+ super(theMethod, theContext, theProvider);
+
+ myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, theContext);
+ }
+
+ @Override
+ public String getResourceName() {
+ return null;
+ }
+
+ @Override
+ public RestOperationTypeEnum getRestOperationType() {
+ return RestOperationTypeEnum.GRAPHQL_REQUEST;
+ }
+
+ @Override
+ public boolean isGlobalMethod() {
+ return true;
+ }
+
+ @Override
+ public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
+ if ("$graphql".equals(theRequest.getOperation())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public Object invokeServer(IRestfulServer> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
+ Object[] methodParams = createMethodParams(theRequest);
+ if (myIdParamIndex != null) {
+ methodParams[myIdParamIndex] = theRequest.getId();
+ }
+
+ Object response = invokeServerMethod(theServer, theRequest, methodParams);
+
+ int statusCode = Constants.STATUS_HTTP_200_OK;
+ String statusMessage = Constants.HTTP_STATUS_NAMES.get(statusCode);
+ String contentType = Constants.CT_JSON;
+ String charset = Constants.CHARSET_NAME_UTF8;
+ boolean respondGzip = theRequest.isRespondGzip();
+
+ Writer writer = theRequest.getResponse().getResponseWriter(statusCode, statusMessage, contentType, charset, respondGzip);
+
+ String responseString = (String) response;
+ writer.write(responseString);
+ writer.close();
+
+ return null;
+ }
+}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLQueryParameter.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLQueryParameter.java
new file mode 100644
index 00000000000..bce42c72d9b
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/GraphQLQueryParameter.java
@@ -0,0 +1,64 @@
+package ca.uhn.fhir.rest.server.method;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2017 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.model.primitive.IntegerDt;
+import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.rest.annotation.Count;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.ParameterUtil;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+public class GraphQLQueryParameter implements IParameter {
+
+ private Class> myType;
+
+ @Override
+ public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding> theMethodBinding) throws InternalErrorException, InvalidRequestException {
+ String[] queryParams = theRequest.getParameters().get(Constants.PARAM_GRAPHQL_QUERY);
+ String retVal = null;
+ if (queryParams != null) {
+ if (queryParams.length > 0) {
+ retVal = queryParams[0];
+ }
+ }
+ return retVal;
+ }
+
+ @Override
+ public void initializeTypes(Method theMethod, Class extends Collection>> theOuterCollectionType, Class extends Collection>> theInnerCollectionType, Class> theParameterType) {
+ if (theOuterCollectionType != null) {
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Count.class.getName() + " but can not be of collection type");
+ }
+ if (!String.class.equals(theParameterType)) {
+ throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Count.class.getName() + " but type '" + theParameterType + "' is an invalid type, must be one of Integer or IntegerType");
+ }
+ myType = theParameterType;
+ }
+
+}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
index 08ceb897ec1..5d0241f87d8 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/MethodUtil.java
@@ -29,6 +29,7 @@ import java.util.*;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException;
@@ -97,7 +98,7 @@ public class MethodUtil {
param = new ServletRequestParameter();
} else if (ServletResponse.class.isAssignableFrom(parameterType)) {
param = new ServletResponseParameter();
- } else if (parameterType.equals(RequestDetails.class)) {
+ } else if (parameterType.equals(RequestDetails.class) || parameterType.equals(ServletRequestDetails.class)) {
param = new RequestDetailsParameter();
} else if (parameterType.equals(IRequestOperationCallback.class)) {
param = new RequestOperationCallbackParameter();
@@ -183,6 +184,8 @@ public class MethodUtil {
((AtParameter) param).setType(theContext, parameterType, innerCollectionType, outerCollectionType);
} else if (nextAnnotation instanceof Count) {
param = new CountParameter();
+ } else if (nextAnnotation instanceof GraphQLQuery) {
+ param = new GraphQLQueryParameter();
} else if (nextAnnotation instanceof Sort) {
param = new SortParameter(theContext);
} else if (nextAnnotation instanceof TransactionParam) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java
index 58d351abb6b..050e2a6d468 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/ReadMethodBinding.java
@@ -142,7 +142,9 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public IBundleProvider invokeServer(IRestfulServer> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
- theMethodParams[myIdIndex] = ParameterUtil.convertIdToType(theRequest.getId(), myIdParameterType);
+ IIdType requestId = theRequest.getId();
+
+ theMethodParams[myIdIndex] = ParameterUtil.convertIdToType(requestId, myIdParameterType);
Object response = invokeServerMethod(theServer, theRequest, theMethodParams);
IBundleProvider retVal = toResourceList(response);
@@ -177,7 +179,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
lastModified = lastModifiedDt.getValue();
}
} else {
- lastModified = ((IAnyResource)responseResource).getMeta().getLastUpdated();
+ lastModified = responseResource.getMeta().getLastUpdated();
}
if (lastModified != null && lastModified.getTime() > ifModifiedSinceDate.getTime()) {
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/GraphQLProviderDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/GraphQLProviderDstu3.java
new file mode 100644
index 00000000000..c3ea1d4656c
--- /dev/null
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/rest/server/GraphQLProviderDstu3.java
@@ -0,0 +1,94 @@
+package org.hl7.fhir.dstu3.hapi.rest.server;
+
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.rest.annotation.GraphQL;
+import ca.uhn.fhir.rest.annotation.GraphQLQuery;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Initialize;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import org.hl7.fhir.dstu3.context.IWorkerContext;
+import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
+import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
+import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Reference;
+import org.hl7.fhir.dstu3.model.Resource;
+import org.hl7.fhir.dstu3.utils.GraphQLEngine;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
+import org.hl7.fhir.utilities.graphql.ObjectValue;
+import org.hl7.fhir.utilities.graphql.Parser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GraphQLProviderDstu3 {
+ private final IWorkerContext myWorkerContext;
+ private Logger ourLog = LoggerFactory.getLogger(GraphQLProviderDstu3.class);
+ private IGraphQLStorageServices myStorageServices;
+
+ /**
+ * Constructor which uses a default context and validation support object
+ *
+ * @param theStorageServices The storage services (this object will be used to retrieve various resources as required by the GraphQL engine)
+ */
+ public GraphQLProviderDstu3(IGraphQLStorageServices theStorageServices) {
+ this(FhirContext.forDstu3(), new DefaultProfileValidationSupport(), theStorageServices);
+ }
+
+ /**
+ * Constructor which uses the given worker context
+ *
+ * @param theFhirContext The HAPI FHIR Context object
+ * @param theValidationSupport The HAPI Validation Support object
+ * @param theStorageServices The storage services (this object will be used to retrieve various resources as required by the GraphQL engine)
+ */
+ public GraphQLProviderDstu3(FhirContext theFhirContext, IValidationSupport theValidationSupport, IGraphQLStorageServices theStorageServices) {
+ myWorkerContext = new HapiWorkerContext(theFhirContext, theValidationSupport);
+ myStorageServices = theStorageServices;
+ }
+
+ @Initialize
+ public void initialize(RestfulServer theServer) {
+ ourLog.trace("Initializing GraphQL provider");
+ if (theServer.getFhirContext().getVersion().getVersion() != FhirVersionEnum.DSTU3) {
+ throw new ConfigurationException("Can not use " + getClass().getName() + " provider on server with FHIR " + theServer.getFhirContext().getVersion().getVersion().name() + " context");
+ }
+ }
+
+ @GraphQL
+ public String graphql(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQuery String theQuery) {
+
+ GraphQLEngine engine = new GraphQLEngine(myWorkerContext);
+ engine.setServices(myStorageServices);
+ try {
+ engine.setGraphQL(Parser.parse(theQuery));
+ } catch (Exception theE) {
+ throw new InvalidRequestException("Unable to parse GraphQL Expression: " + theE.toString());
+ }
+
+ try {
+
+ if (theId != null) {
+ Resource focus = myStorageServices.lookup(theRequestDetails, theId.getResourceType(), theId.getIdPart());
+ engine.setFocus(focus);
+ }
+ engine.execute();
+
+ StringBuilder outputBuilder = new StringBuilder();
+ ObjectValue output = engine.getOutput();
+ output.write(outputBuilder, 0, "\n");
+
+ return outputBuilder.toString();
+
+ } catch (Exception theE) {
+ throw new InvalidRequestException("Unable to execute GraphQL Expression: " + theE.toString());
+ }
+ }
+
+
+}
+
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base.java
index 6064a946fd3..6abc801dc36 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Base.java
@@ -1,697 +1,701 @@
-package org.hl7.fhir.dstu3.model;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.hl7.fhir.dstu3.elementmodel.Element;
-import org.hl7.fhir.exceptions.FHIRException;
-import org.hl7.fhir.instance.model.api.IBase;
-import org.hl7.fhir.utilities.Utilities;
-import org.hl7.fhir.utilities.xhtml.XhtmlNode;
-import org.hl7.fhir.utilities.xhtml.XhtmlParser;
-
-import ca.uhn.fhir.model.api.IElement;
-//Add comment to test SVN
-public abstract class Base implements Serializable, IBase, IElement {
-
- /**
- * User appended data items - allow users to add extra information to the class
- */
-private Map userData;
-
- /**
- * Round tracking xml comments for testing convenience
- */
- private List formatCommentsPre;
-
- /**
- * Round tracking xml comments for testing convenience
- */
- private List formatCommentsPost;
-
-
- public Object getUserData(String name) {
- if (userData == null)
- return null;
- return userData.get(name);
- }
-
- public void setUserData(String name, Object value) {
- if (userData == null)
- userData = new HashMap();
- userData.put(name, value);
- }
-
- public void clearUserData(String name) {
- if (userData != null)
- userData.remove(name);
- }
-
- public void setUserDataINN(String name, Object value) {
- if (value == null)
- return;
-
- if (userData == null)
- userData = new HashMap();
- userData.put(name, value);
- }
-
- public boolean hasUserData(String name) {
- if (userData == null)
- return false;
- else
- return userData.containsKey(name);
- }
-
- public String getUserString(String name) {
- Object ud = getUserData(name);
- if (ud == null)
- return null;
- if (ud instanceof String)
- return (String) ud;
- return ud.toString();
- }
-
- public int getUserInt(String name) {
- if (!hasUserData(name))
- return 0;
- return (Integer) getUserData(name);
- }
-
- public boolean hasFormatComment() {
- return (formatCommentsPre != null && !formatCommentsPre.isEmpty()) || (formatCommentsPost != null && !formatCommentsPost.isEmpty());
- }
-
- public List getFormatCommentsPre() {
- if (formatCommentsPre == null)
- formatCommentsPre = new ArrayList();
- return formatCommentsPre;
- }
-
- public List getFormatCommentsPost() {
- if (formatCommentsPost == null)
- formatCommentsPost = new ArrayList();
- return formatCommentsPost;
- }
-
- // these 3 allow evaluation engines to get access to primitive values
- public boolean isPrimitive() {
- return false;
- }
-
- public boolean hasPrimitiveValue() {
- return isPrimitive();
- }
-
- public String primitiveValue() {
- return null;
- }
-
- public abstract String fhirType() ;
-
- public boolean hasType(String... name) {
- String t = fhirType();
- for (String n : name)
- if (n.equalsIgnoreCase(t))
- return true;
- return false;
- }
-
- protected abstract void listChildren(List result) ;
-
- public Base setProperty(String name, Base value) throws FHIRException {
- throw new FHIRException("Attempt to set unknown property "+name);
- }
-
- public Base addChild(String name) throws FHIRException {
- throw new FHIRException("Attempt to add child with unknown name "+name);
- }
-
- /**
- * Supports iterating the children elements in some generic processor or browser
- * All defined children will be listed, even if they have no value on this instance
- *
- * Note that the actual content of primitive or xhtml elements is not iterated explicitly.
- * To find these, the processing code must recognise the element as a primitive, typecast
- * the value to a {@link Type}, and examine the value
- *
- * @return a list of all the children defined for this element
- */
- public List children() {
- List result = new ArrayList();
- listChildren(result);
- return result;
- }
-
- public Property getChildByName(String name) {
- List children = new ArrayList();
- listChildren(children);
- for (Property c : children)
- if (c.getName().equals(name))
- return c;
- return null;
- }
-
- public List listChildrenByName(String name) throws FHIRException {
- List result = new ArrayList();
- for (Base b : listChildrenByName(name, true))
- if (b != null)
- result.add(b);
- return result;
- }
-
- public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException {
- if (name.equals("*")) {
- List children = new ArrayList();
- listChildren(children);
- List result = new ArrayList();
- for (Property c : children)
- result.addAll(c.getValues());
- return result.toArray(new Base[result.size()]);
- }
- else
- return getProperty(name.hashCode(), name, checkValid);
- }
-
- public boolean isEmpty() {
- return true; // userData does not count
- }
-
- public boolean equalsDeep(Base other) {
- return other != null;
- }
-
- public boolean equalsShallow(Base other) {
- return other != null;
- }
-
- public static boolean compareDeep(List extends Base> e1, List extends Base> e2, boolean allowNull) {
- if (noList(e1) && noList(e2) && allowNull)
- return true;
- if (noList(e1) || noList(e2))
- return false;
- if (e1.size() != e2.size())
- return false;
- for (int i = 0; i < e1.size(); i++) {
- if (!compareDeep(e1.get(i), e2.get(i), allowNull))
- return false;
- }
- return true;
- }
-
- private static boolean noList(List extends Base> list) {
- return list == null || list.isEmpty();
- }
-
- public static boolean compareDeep(Base e1, Base e2, boolean allowNull) {
- if (allowNull) {
- boolean noLeft = e1 == null || e1.isEmpty();
- boolean noRight = e2 == null || e2.isEmpty();
- if (noLeft && noRight) {
- return true;
- }
- }
- if (e1 == null || e2 == null)
- return false;
- if (e2.isMetadataBased() && !e1.isMetadataBased()) // respect existing order for debugging consistency; outcome must be the same either way
- return e2.equalsDeep(e1);
- else
- return e1.equalsDeep(e2);
- }
-
- public static boolean compareDeep(XhtmlNode div1, XhtmlNode div2, boolean allowNull) {
- if (div1 == null && div2 == null && allowNull)
- return true;
- if (div1 == null || div2 == null)
- return false;
- return div1.equalsDeep(div2);
- }
-
-
- public static boolean compareValues(List extends PrimitiveType> e1, List extends PrimitiveType> e2, boolean allowNull) {
- if (e1 == null && e2 == null && allowNull)
- return true;
- if (e1 == null || e2 == null)
- return false;
- if (e1.size() != e2.size())
- return false;
- for (int i = 0; i < e1.size(); i++) {
- if (!compareValues(e1.get(i), e2.get(i), allowNull))
- return false;
- }
- return true;
- }
-
- public static boolean compareValues(PrimitiveType e1, PrimitiveType e2, boolean allowNull) {
- boolean noLeft = e1 == null || e1.isEmpty();
- boolean noRight = e2 == null || e2.isEmpty();
- if (noLeft && noRight && allowNull) {
- return true;
- }
- if (noLeft != noRight)
- return false;
- return e1.equalsShallow(e2);
- }
-
- // -- converters for property setters
-
- public Type castToType(Base b) throws FHIRException {
- if (b instanceof Type)
- return (Type) b;
- else if (b.isMetadataBased())
- return ((org.hl7.fhir.dstu3.elementmodel.Element) b).asType();
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Reference");
- }
-
-
- public BooleanType castToBoolean(Base b) throws FHIRException {
- if (b instanceof BooleanType)
- return (BooleanType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Boolean");
- }
-
- public IntegerType castToInteger(Base b) throws FHIRException {
- if (b instanceof IntegerType)
- return (IntegerType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Integer");
- }
-
- public DecimalType castToDecimal(Base b) throws FHIRException {
- if (b instanceof DecimalType)
- return (DecimalType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Decimal");
- }
-
- public Base64BinaryType castToBase64Binary(Base b) throws FHIRException {
- if (b instanceof Base64BinaryType)
- return (Base64BinaryType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Base64Binary");
- }
-
- public InstantType castToInstant(Base b) throws FHIRException {
- if (b instanceof InstantType)
- return (InstantType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Instant");
- }
-
- public StringType castToString(Base b) throws FHIRException {
- if (b instanceof StringType)
- return (StringType) b;
- else if (b.hasPrimitiveValue())
- return new StringType(b.primitiveValue());
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a String");
- }
-
- public UriType castToUri(Base b) throws FHIRException {
- if (b instanceof UriType)
- return (UriType) b;
- else if (b.hasPrimitiveValue())
- return new UriType(b.primitiveValue());
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Uri");
- }
-
- public DateType castToDate(Base b) throws FHIRException {
- if (b instanceof DateType)
- return (DateType) b;
- else if (b.hasPrimitiveValue())
- return new DateType(b.primitiveValue());
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Date");
- }
-
- public DateTimeType castToDateTime(Base b) throws FHIRException {
- if (b instanceof DateTimeType)
- return (DateTimeType) b;
- else if (b.fhirType().equals("dateTime"))
- return new DateTimeType(b.primitiveValue());
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a DateTime");
- }
-
- public TimeType castToTime(Base b) throws FHIRException {
- if (b instanceof TimeType)
- return (TimeType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Time");
- }
-
- public CodeType castToCode(Base b) throws FHIRException {
- if (b instanceof CodeType)
- return (CodeType) b;
- else if (b.isPrimitive())
- return new CodeType(b.primitiveValue());
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Code");
- }
-
- public OidType castToOid(Base b) throws FHIRException {
- if (b instanceof OidType)
- return (OidType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Oid");
- }
-
- public IdType castToId(Base b) throws FHIRException {
- if (b instanceof IdType)
- return (IdType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Id");
- }
-
- public UnsignedIntType castToUnsignedInt(Base b) throws FHIRException {
- if (b instanceof UnsignedIntType)
- return (UnsignedIntType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a UnsignedInt");
- }
-
- public PositiveIntType castToPositiveInt(Base b) throws FHIRException {
- if (b instanceof PositiveIntType)
- return (PositiveIntType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a PositiveInt");
- }
-
- public MarkdownType castToMarkdown(Base b) throws FHIRException {
- if (b instanceof MarkdownType)
- return (MarkdownType) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Markdown");
- }
-
- public Annotation castToAnnotation(Base b) throws FHIRException {
- if (b instanceof Annotation)
- return (Annotation) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Annotation");
- }
-
- public Dosage castToDosage(Base b) throws FHIRException {
- if (b instanceof Dosage)
- return (Dosage) b;
- else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction");
- }
-
-
- public Attachment castToAttachment(Base b) throws FHIRException {
- if (b instanceof Attachment)
- return (Attachment) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Attachment");
- }
-
- public Identifier castToIdentifier(Base b) throws FHIRException {
- if (b instanceof Identifier)
- return (Identifier) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Identifier");
- }
-
- public CodeableConcept castToCodeableConcept(Base b) throws FHIRException {
- if (b instanceof CodeableConcept)
- return (CodeableConcept) b;
- else if (b instanceof CodeType) {
- CodeableConcept cc = new CodeableConcept();
- cc.addCoding().setCode(((CodeType) b).asStringValue());
- return cc;
- } else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a CodeableConcept");
- }
-
- public Coding castToCoding(Base b) throws FHIRException {
- if (b instanceof Coding)
- return (Coding) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Coding");
- }
-
- public Quantity castToQuantity(Base b) throws FHIRException {
- if (b instanceof Quantity)
- return (Quantity) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Quantity");
- }
-
- public Money castToMoney(Base b) throws FHIRException {
- if (b instanceof Money)
- return (Money) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Money");
- }
-
- public Duration castToDuration(Base b) throws FHIRException {
- if (b instanceof Duration)
- return (Duration) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Duration");
- }
-
- public SimpleQuantity castToSimpleQuantity(Base b) throws FHIRException {
- if (b instanceof SimpleQuantity)
- return (SimpleQuantity) b;
- else if (b instanceof Quantity) {
- Quantity q = (Quantity) b;
- SimpleQuantity sq = new SimpleQuantity();
- sq.setValueElement(q.getValueElement());
- sq.setComparatorElement(q.getComparatorElement());
- sq.setUnitElement(q.getUnitElement());
- sq.setSystemElement(q.getSystemElement());
- sq.setCodeElement(q.getCodeElement());
- return sq;
- } else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an SimpleQuantity");
- }
-
- public Range castToRange(Base b) throws FHIRException {
- if (b instanceof Range)
- return (Range) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Range");
- }
-
- public Period castToPeriod(Base b) throws FHIRException {
- if (b instanceof Period)
- return (Period) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Period");
- }
-
- public Ratio castToRatio(Base b) throws FHIRException {
- if (b instanceof Ratio)
- return (Ratio) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Ratio");
- }
-
- public SampledData castToSampledData(Base b) throws FHIRException {
- if (b instanceof SampledData)
- return (SampledData) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a SampledData");
- }
-
- public Signature castToSignature(Base b) throws FHIRException {
- if (b instanceof Signature)
- return (Signature) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Signature");
- }
-
- public HumanName castToHumanName(Base b) throws FHIRException {
- if (b instanceof HumanName)
- return (HumanName) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a HumanName");
- }
-
- public Address castToAddress(Base b) throws FHIRException {
- if (b instanceof Address)
- return (Address) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Address");
- }
-
- public ContactDetail castToContactDetail(Base b) throws FHIRException {
- if (b instanceof ContactDetail)
- return (ContactDetail) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ContactDetail");
- }
-
- public Contributor castToContributor(Base b) throws FHIRException {
- if (b instanceof Contributor)
- return (Contributor) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Contributor");
- }
-
- public UsageContext castToUsageContext(Base b) throws FHIRException {
- if (b instanceof UsageContext)
- return (UsageContext) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a UsageContext");
- }
-
- public RelatedArtifact castToRelatedArtifact(Base b) throws FHIRException {
- if (b instanceof RelatedArtifact)
- return (RelatedArtifact) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a RelatedArtifact");
- }
-
- public ContactPoint castToContactPoint(Base b) throws FHIRException {
- if (b instanceof ContactPoint)
- return (ContactPoint) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ContactPoint");
- }
-
- public Timing castToTiming(Base b) throws FHIRException {
- if (b instanceof Timing)
- return (Timing) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Timing");
- }
-
- public Reference castToReference(Base b) throws FHIRException {
- if (b instanceof Reference)
- return (Reference) b;
- else if (b.isPrimitive() && Utilities.isURL(b.primitiveValue()))
- return new Reference().setReference(b.primitiveValue());
- else if (b instanceof org.hl7.fhir.dstu3.elementmodel.Element && b.fhirType().equals("Reference")) {
- org.hl7.fhir.dstu3.elementmodel.Element e = (org.hl7.fhir.dstu3.elementmodel.Element) b;
- return new Reference().setReference(e.getChildValue("reference")).setDisplay(e.getChildValue("display"));
- } else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Reference");
- }
-
- public Meta castToMeta(Base b) throws FHIRException {
- if (b instanceof Meta)
- return (Meta) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Meta");
- }
-
- public Extension castToExtension(Base b) throws FHIRException {
- if (b instanceof Extension)
- return (Extension) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Extension");
- }
-
- public Resource castToResource(Base b) throws FHIRException {
- if (b instanceof Resource)
- return (Resource) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Resource");
- }
-
- public Narrative castToNarrative(Base b) throws FHIRException {
- if (b instanceof Narrative)
- return (Narrative) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Narrative");
- }
-
-
- public ElementDefinition castToElementDefinition(Base b) throws FHIRException {
- if (b instanceof ElementDefinition)
- return (ElementDefinition) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ElementDefinition");
- }
-
- public DataRequirement castToDataRequirement(Base b) throws FHIRException {
- if (b instanceof DataRequirement)
- return (DataRequirement) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a DataRequirement");
- }
-
- public ParameterDefinition castToParameterDefinition(Base b) throws FHIRException {
- if (b instanceof ParameterDefinition)
- return (ParameterDefinition) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ParameterDefinition");
- }
-
- public TriggerDefinition castToTriggerDefinition(Base b) throws FHIRException {
- if (b instanceof TriggerDefinition)
- return (TriggerDefinition) b;
- else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a TriggerDefinition");
- }
-
- public XhtmlNode castToXhtml(Base b) throws FHIRException {
- if (b instanceof Element) {
- return ((Element) b).getXhtml();
- } else if (b instanceof StringType) {
- try {
- return new XhtmlParser().parseFragment(((StringType) b).asStringValue());
- } catch (IOException e) {
- throw new FHIRException(e);
- }
- } else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to XHtml");
- }
-
- public String castToXhtmlString(Base b) throws FHIRException {
- if (b instanceof Element) {
- return ((Element) b).getValue();
- } else if (b instanceof StringType) {
- return ((StringType) b).asStringValue();
- } else
- throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to XHtml string");
- }
-
- protected boolean isMetadataBased() {
- return false;
- }
-
- public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
- if (checkValid)
- throw new FHIRException("Attempt to read invalid property '"+name+"' on type "+fhirType());
- return null;
- }
-
- public Base setProperty(int hash, String name, Base value) throws FHIRException {
- throw new FHIRException("Attempt to write to invalid property '"+name+"' on type "+fhirType());
- }
-
- public Base makeProperty(int hash, String name) throws FHIRException {
- throw new FHIRException("Attempt to make an invalid property '"+name+"' on type "+fhirType());
- }
-
- public String[] getTypesForProperty(int hash, String name) throws FHIRException {
- throw new FHIRException("Attempt to get types for an invalid property '"+name+"' on type "+fhirType());
- }
-
- public static boolean equals(String v1, String v2) {
- if (v1 == null && v2 == null)
- return true;
- else if (v1 == null || v2 == null)
- return false;
- else
- return v1.equals(v2);
- }
-
- public boolean isResource() {
- return false;
- }
-
-
- public abstract String getIdBase();
- public abstract void setIdBase(String value);
-}
+package org.hl7.fhir.dstu3.model;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hl7.fhir.dstu3.elementmodel.Element;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.utilities.Utilities;
+import org.hl7.fhir.utilities.xhtml.XhtmlNode;
+import org.hl7.fhir.utilities.xhtml.XhtmlParser;
+
+import ca.uhn.fhir.model.api.IElement;
+//Add comment to test SVN
+public abstract class Base implements Serializable, IBase, IElement {
+
+ /**
+ * User appended data items - allow users to add extra information to the class
+ */
+private Map userData;
+
+ /**
+ * Round tracking xml comments for testing convenience
+ */
+ private List formatCommentsPre;
+
+ /**
+ * Round tracking xml comments for testing convenience
+ */
+ private List formatCommentsPost;
+
+
+ public Object getUserData(String name) {
+ if (userData == null)
+ return null;
+ return userData.get(name);
+ }
+
+ public void setUserData(String name, Object value) {
+ if (userData == null)
+ userData = new HashMap();
+ userData.put(name, value);
+ }
+
+ public void clearUserData(String name) {
+ if (userData != null)
+ userData.remove(name);
+ }
+
+ public void setUserDataINN(String name, Object value) {
+ if (value == null)
+ return;
+
+ if (userData == null)
+ userData = new HashMap();
+ userData.put(name, value);
+ }
+
+ public boolean hasUserData(String name) {
+ if (userData == null)
+ return false;
+ else
+ return userData.containsKey(name);
+ }
+
+ public String getUserString(String name) {
+ Object ud = getUserData(name);
+ if (ud == null)
+ return null;
+ if (ud instanceof String)
+ return (String) ud;
+ return ud.toString();
+ }
+
+ public int getUserInt(String name) {
+ if (!hasUserData(name))
+ return 0;
+ return (Integer) getUserData(name);
+ }
+
+ public boolean hasFormatComment() {
+ return (formatCommentsPre != null && !formatCommentsPre.isEmpty()) || (formatCommentsPost != null && !formatCommentsPost.isEmpty());
+ }
+
+ public List getFormatCommentsPre() {
+ if (formatCommentsPre == null)
+ formatCommentsPre = new ArrayList();
+ return formatCommentsPre;
+ }
+
+ public List getFormatCommentsPost() {
+ if (formatCommentsPost == null)
+ formatCommentsPost = new ArrayList();
+ return formatCommentsPost;
+ }
+
+ // these 3 allow evaluation engines to get access to primitive values
+ public boolean isPrimitive() {
+ return false;
+ }
+
+ public boolean hasPrimitiveValue() {
+ return isPrimitive();
+ }
+
+ public String primitiveValue() {
+ return null;
+ }
+
+ public abstract String fhirType() ;
+
+ public boolean hasType(String... name) {
+ String t = fhirType();
+ for (String n : name)
+ if (n.equalsIgnoreCase(t))
+ return true;
+ return false;
+ }
+
+ protected abstract void listChildren(List result) ;
+
+ public Base setProperty(String name, Base value) throws FHIRException {
+ throw new FHIRException("Attempt to set unknown property "+name);
+ }
+
+ public Base addChild(String name) throws FHIRException {
+ throw new FHIRException("Attempt to add child with unknown name "+name);
+ }
+
+ /**
+ * Supports iterating the children elements in some generic processor or browser
+ * All defined children will be listed, even if they have no value on this instance
+ *
+ * Note that the actual content of primitive or xhtml elements is not iterated explicitly.
+ * To find these, the processing code must recognise the element as a primitive, typecast
+ * the value to a {@link Type}, and examine the value
+ *
+ * @return a list of all the children defined for this element
+ */
+ public List children() {
+ List result = new ArrayList();
+ listChildren(result);
+ return result;
+ }
+
+ public Property getChildByName(String name) {
+ List children = new ArrayList();
+ listChildren(children);
+ for (Property c : children)
+ if (c.getName().equals(name))
+ return c;
+ return null;
+ }
+
+ public List listChildrenByName(String name) throws FHIRException {
+ List result = new ArrayList();
+ for (Base b : listChildrenByName(name, true))
+ if (b != null)
+ result.add(b);
+ return result;
+ }
+
+ public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException {
+ if (name.equals("*")) {
+ List children = new ArrayList();
+ listChildren(children);
+ List result = new ArrayList();
+ for (Property c : children)
+ result.addAll(c.getValues());
+ return result.toArray(new Base[result.size()]);
+ }
+ else
+ return getProperty(name.hashCode(), name, checkValid);
+ }
+
+ public boolean isEmpty() {
+ return true; // userData does not count
+ }
+
+ public boolean equalsDeep(Base other) {
+ return other != null;
+ }
+
+ public boolean equalsShallow(Base other) {
+ return other != null;
+ }
+
+ public static boolean compareDeep(List extends Base> e1, List extends Base> e2, boolean allowNull) {
+ if (noList(e1) && noList(e2) && allowNull)
+ return true;
+ if (noList(e1) || noList(e2))
+ return false;
+ if (e1.size() != e2.size())
+ return false;
+ for (int i = 0; i < e1.size(); i++) {
+ if (!compareDeep(e1.get(i), e2.get(i), allowNull))
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean noList(List extends Base> list) {
+ return list == null || list.isEmpty();
+ }
+
+ public static boolean compareDeep(Base e1, Base e2, boolean allowNull) {
+ if (allowNull) {
+ boolean noLeft = e1 == null || e1.isEmpty();
+ boolean noRight = e2 == null || e2.isEmpty();
+ if (noLeft && noRight) {
+ return true;
+ }
+ }
+ if (e1 == null || e2 == null)
+ return false;
+ if (e2.isMetadataBased() && !e1.isMetadataBased()) // respect existing order for debugging consistency; outcome must be the same either way
+ return e2.equalsDeep(e1);
+ else
+ return e1.equalsDeep(e2);
+ }
+
+ public static boolean compareDeep(XhtmlNode div1, XhtmlNode div2, boolean allowNull) {
+ if (div1 == null && div2 == null && allowNull)
+ return true;
+ if (div1 == null || div2 == null)
+ return false;
+ return div1.equalsDeep(div2);
+ }
+
+
+ public static boolean compareValues(List extends PrimitiveType> e1, List extends PrimitiveType> e2, boolean allowNull) {
+ if (e1 == null && e2 == null && allowNull)
+ return true;
+ if (e1 == null || e2 == null)
+ return false;
+ if (e1.size() != e2.size())
+ return false;
+ for (int i = 0; i < e1.size(); i++) {
+ if (!compareValues(e1.get(i), e2.get(i), allowNull))
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean compareValues(PrimitiveType e1, PrimitiveType e2, boolean allowNull) {
+ boolean noLeft = e1 == null || e1.isEmpty();
+ boolean noRight = e2 == null || e2.isEmpty();
+ if (noLeft && noRight && allowNull) {
+ return true;
+ }
+ if (noLeft != noRight)
+ return false;
+ return e1.equalsShallow(e2);
+ }
+
+ // -- converters for property setters
+
+ public Type castToType(Base b) throws FHIRException {
+ if (b instanceof Type)
+ return (Type) b;
+ else if (b.isMetadataBased())
+ return ((org.hl7.fhir.dstu3.elementmodel.Element) b).asType();
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Reference");
+ }
+
+
+ public BooleanType castToBoolean(Base b) throws FHIRException {
+ if (b instanceof BooleanType)
+ return (BooleanType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Boolean");
+ }
+
+ public IntegerType castToInteger(Base b) throws FHIRException {
+ if (b instanceof IntegerType)
+ return (IntegerType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Integer");
+ }
+
+ public DecimalType castToDecimal(Base b) throws FHIRException {
+ if (b instanceof DecimalType)
+ return (DecimalType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Decimal");
+ }
+
+ public Base64BinaryType castToBase64Binary(Base b) throws FHIRException {
+ if (b instanceof Base64BinaryType)
+ return (Base64BinaryType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Base64Binary");
+ }
+
+ public InstantType castToInstant(Base b) throws FHIRException {
+ if (b instanceof InstantType)
+ return (InstantType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Instant");
+ }
+
+ public StringType castToString(Base b) throws FHIRException {
+ if (b instanceof StringType)
+ return (StringType) b;
+ else if (b.hasPrimitiveValue())
+ return new StringType(b.primitiveValue());
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a String");
+ }
+
+ public UriType castToUri(Base b) throws FHIRException {
+ if (b instanceof UriType)
+ return (UriType) b;
+ else if (b.hasPrimitiveValue())
+ return new UriType(b.primitiveValue());
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Uri");
+ }
+
+ public DateType castToDate(Base b) throws FHIRException {
+ if (b instanceof DateType)
+ return (DateType) b;
+ else if (b.hasPrimitiveValue())
+ return new DateType(b.primitiveValue());
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Date");
+ }
+
+ public DateTimeType castToDateTime(Base b) throws FHIRException {
+ if (b instanceof DateTimeType)
+ return (DateTimeType) b;
+ else if (b.fhirType().equals("dateTime"))
+ return new DateTimeType(b.primitiveValue());
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a DateTime");
+ }
+
+ public TimeType castToTime(Base b) throws FHIRException {
+ if (b instanceof TimeType)
+ return (TimeType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Time");
+ }
+
+ public CodeType castToCode(Base b) throws FHIRException {
+ if (b instanceof CodeType)
+ return (CodeType) b;
+ else if (b.isPrimitive())
+ return new CodeType(b.primitiveValue());
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Code");
+ }
+
+ public OidType castToOid(Base b) throws FHIRException {
+ if (b instanceof OidType)
+ return (OidType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Oid");
+ }
+
+ public IdType castToId(Base b) throws FHIRException {
+ if (b instanceof IdType)
+ return (IdType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Id");
+ }
+
+ public UnsignedIntType castToUnsignedInt(Base b) throws FHIRException {
+ if (b instanceof UnsignedIntType)
+ return (UnsignedIntType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a UnsignedInt");
+ }
+
+ public PositiveIntType castToPositiveInt(Base b) throws FHIRException {
+ if (b instanceof PositiveIntType)
+ return (PositiveIntType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a PositiveInt");
+ }
+
+ public MarkdownType castToMarkdown(Base b) throws FHIRException {
+ if (b instanceof MarkdownType)
+ return (MarkdownType) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Markdown");
+ }
+
+ public Annotation castToAnnotation(Base b) throws FHIRException {
+ if (b instanceof Annotation)
+ return (Annotation) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Annotation");
+ }
+
+ public Dosage castToDosage(Base b) throws FHIRException {
+ if (b instanceof Dosage)
+ return (Dosage) b;
+ else throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an DosageInstruction");
+ }
+
+
+ public Attachment castToAttachment(Base b) throws FHIRException {
+ if (b instanceof Attachment)
+ return (Attachment) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Attachment");
+ }
+
+ public Identifier castToIdentifier(Base b) throws FHIRException {
+ if (b instanceof Identifier)
+ return (Identifier) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Identifier");
+ }
+
+ public CodeableConcept castToCodeableConcept(Base b) throws FHIRException {
+ if (b instanceof CodeableConcept)
+ return (CodeableConcept) b;
+ else if (b instanceof CodeType) {
+ CodeableConcept cc = new CodeableConcept();
+ cc.addCoding().setCode(((CodeType) b).asStringValue());
+ return cc;
+ } else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a CodeableConcept");
+ }
+
+ public Coding castToCoding(Base b) throws FHIRException {
+ if (b instanceof Coding)
+ return (Coding) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Coding");
+ }
+
+ public Quantity castToQuantity(Base b) throws FHIRException {
+ if (b instanceof Quantity)
+ return (Quantity) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Quantity");
+ }
+
+ public Money castToMoney(Base b) throws FHIRException {
+ if (b instanceof Money)
+ return (Money) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Money");
+ }
+
+ public Duration castToDuration(Base b) throws FHIRException {
+ if (b instanceof Duration)
+ return (Duration) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an Duration");
+ }
+
+ public SimpleQuantity castToSimpleQuantity(Base b) throws FHIRException {
+ if (b instanceof SimpleQuantity)
+ return (SimpleQuantity) b;
+ else if (b instanceof Quantity) {
+ Quantity q = (Quantity) b;
+ SimpleQuantity sq = new SimpleQuantity();
+ sq.setValueElement(q.getValueElement());
+ sq.setComparatorElement(q.getComparatorElement());
+ sq.setUnitElement(q.getUnitElement());
+ sq.setSystemElement(q.getSystemElement());
+ sq.setCodeElement(q.getCodeElement());
+ return sq;
+ } else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to an SimpleQuantity");
+ }
+
+ public Range castToRange(Base b) throws FHIRException {
+ if (b instanceof Range)
+ return (Range) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Range");
+ }
+
+ public Period castToPeriod(Base b) throws FHIRException {
+ if (b instanceof Period)
+ return (Period) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Period");
+ }
+
+ public Ratio castToRatio(Base b) throws FHIRException {
+ if (b instanceof Ratio)
+ return (Ratio) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Ratio");
+ }
+
+ public SampledData castToSampledData(Base b) throws FHIRException {
+ if (b instanceof SampledData)
+ return (SampledData) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a SampledData");
+ }
+
+ public Signature castToSignature(Base b) throws FHIRException {
+ if (b instanceof Signature)
+ return (Signature) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Signature");
+ }
+
+ public HumanName castToHumanName(Base b) throws FHIRException {
+ if (b instanceof HumanName)
+ return (HumanName) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a HumanName");
+ }
+
+ public Address castToAddress(Base b) throws FHIRException {
+ if (b instanceof Address)
+ return (Address) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Address");
+ }
+
+ public ContactDetail castToContactDetail(Base b) throws FHIRException {
+ if (b instanceof ContactDetail)
+ return (ContactDetail) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ContactDetail");
+ }
+
+ public Contributor castToContributor(Base b) throws FHIRException {
+ if (b instanceof Contributor)
+ return (Contributor) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Contributor");
+ }
+
+ public UsageContext castToUsageContext(Base b) throws FHIRException {
+ if (b instanceof UsageContext)
+ return (UsageContext) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a UsageContext");
+ }
+
+ public RelatedArtifact castToRelatedArtifact(Base b) throws FHIRException {
+ if (b instanceof RelatedArtifact)
+ return (RelatedArtifact) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a RelatedArtifact");
+ }
+
+ public ContactPoint castToContactPoint(Base b) throws FHIRException {
+ if (b instanceof ContactPoint)
+ return (ContactPoint) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ContactPoint");
+ }
+
+ public Timing castToTiming(Base b) throws FHIRException {
+ if (b instanceof Timing)
+ return (Timing) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Timing");
+ }
+
+ public Reference castToReference(Base b) throws FHIRException {
+ if (b instanceof Reference)
+ return (Reference) b;
+ else if (b.isPrimitive() && Utilities.isURL(b.primitiveValue()))
+ return new Reference().setReference(b.primitiveValue());
+ else if (b instanceof org.hl7.fhir.dstu3.elementmodel.Element && b.fhirType().equals("Reference")) {
+ org.hl7.fhir.dstu3.elementmodel.Element e = (org.hl7.fhir.dstu3.elementmodel.Element) b;
+ return new Reference().setReference(e.getChildValue("reference")).setDisplay(e.getChildValue("display"));
+ } else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Reference");
+ }
+
+ public Meta castToMeta(Base b) throws FHIRException {
+ if (b instanceof Meta)
+ return (Meta) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Meta");
+ }
+
+ public Extension castToExtension(Base b) throws FHIRException {
+ if (b instanceof Extension)
+ return (Extension) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Extension");
+ }
+
+ public Resource castToResource(Base b) throws FHIRException {
+ if (b instanceof Resource)
+ return (Resource) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Resource");
+ }
+
+ public Narrative castToNarrative(Base b) throws FHIRException {
+ if (b instanceof Narrative)
+ return (Narrative) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a Narrative");
+ }
+
+
+ public ElementDefinition castToElementDefinition(Base b) throws FHIRException {
+ if (b instanceof ElementDefinition)
+ return (ElementDefinition) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ElementDefinition");
+ }
+
+ public DataRequirement castToDataRequirement(Base b) throws FHIRException {
+ if (b instanceof DataRequirement)
+ return (DataRequirement) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a DataRequirement");
+ }
+
+ public ParameterDefinition castToParameterDefinition(Base b) throws FHIRException {
+ if (b instanceof ParameterDefinition)
+ return (ParameterDefinition) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a ParameterDefinition");
+ }
+
+ public TriggerDefinition castToTriggerDefinition(Base b) throws FHIRException {
+ if (b instanceof TriggerDefinition)
+ return (TriggerDefinition) b;
+ else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to a TriggerDefinition");
+ }
+
+ public XhtmlNode castToXhtml(Base b) throws FHIRException {
+ if (b instanceof Element) {
+ return ((Element) b).getXhtml();
+ } else if (b instanceof StringType) {
+ try {
+ return new XhtmlParser().parseFragment(((StringType) b).asStringValue());
+ } catch (IOException e) {
+ throw new FHIRException(e);
+ }
+ } else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to XHtml");
+ }
+
+ public String castToXhtmlString(Base b) throws FHIRException {
+ if (b instanceof Element) {
+ return ((Element) b).getValue();
+ } else if (b instanceof StringType) {
+ return ((StringType) b).asStringValue();
+ } else
+ throw new FHIRException("Unable to convert a "+b.getClass().getName()+" to XHtml string");
+ }
+
+ protected boolean isMetadataBased() {
+ return false;
+ }
+
+ public Property getNamedProperty(String _name) throws FHIRException {
+ return getChildByName(_name);
+ }
+
+ public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
+ if (checkValid)
+ throw new FHIRException("Attempt to read invalid property '"+name+"' on type "+fhirType());
+ return null;
+ }
+
+ public Base setProperty(int hash, String name, Base value) throws FHIRException {
+ throw new FHIRException("Attempt to write to invalid property '"+name+"' on type "+fhirType());
+ }
+
+ public Base makeProperty(int hash, String name) throws FHIRException {
+ throw new FHIRException("Attempt to make an invalid property '"+name+"' on type "+fhirType());
+ }
+
+ public String[] getTypesForProperty(int hash, String name) throws FHIRException {
+ throw new FHIRException("Attempt to get types for an invalid property '"+name+"' on type "+fhirType());
+ }
+
+ public static boolean equals(String v1, String v2) {
+ if (v1 == null && v2 == null)
+ return true;
+ else if (v1 == null || v2 == null)
+ return false;
+ else
+ return v1.equals(v2);
+ }
+
+ public boolean isResource() {
+ return false;
+ }
+
+
+ public abstract String getIdBase();
+ public abstract void setIdBase(String value);
+}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java
index 267560f209c..6385fe48202 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Property.java
@@ -1,140 +1,143 @@
-package org.hl7.fhir.dstu3.model;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A child element or property defined by the FHIR specification
- * This class is defined as a helper class when iterating the
- * children of an element in a generic fashion
- *
- * At present, iteration is only based on the specification, but
- * this may be changed to allow profile based expression at a
- * later date
- *
- * note: there's no point in creating one of these classes outside this package
- */
-public class Property {
-
- /**
- * The name of the property as found in the FHIR specification
- */
- private String name;
-
- /**
- * The type of the property as specified in the FHIR specification (e.g. type|type|Reference(Name|Name)
- */
- private String typeCode;
-
- /**
- * The formal definition of the element given in the FHIR specification
- */
- private String definition;
-
- /**
- * The minimum allowed cardinality - 0 or 1 when based on the specification
- */
- private int minCardinality;
-
- /**
- * The maximum allowed cardinality - 1 or MAX_INT when based on the specification
- */
- private int maxCardinality;
-
- /**
- * The actual elements that exist on this instance
- */
- private List values = new ArrayList();
-
- /**
- * For run time, if/once a property is hooked up to it's definition
- */
- private StructureDefinition structure;
-
- /**
- * Internal constructor
- */
- public Property(String name, String typeCode, String definition, int minCardinality, int maxCardinality, Base value) {
- super();
- this.name = name;
- this.typeCode = typeCode;
- this.definition = definition;
- this.minCardinality = minCardinality;
- this.maxCardinality = maxCardinality;
- this.values.add(value);
- }
-
- /**
- * Internal constructor
- */
- public Property(String name, String typeCode, String definition, int minCardinality, int maxCardinality, List extends Base> values) {
- super();
- this.name = name;
- this.typeCode = typeCode;
- this.definition = definition;
- this.minCardinality = minCardinality;
- this.maxCardinality = maxCardinality;
- if (values != null)
- this.values.addAll(values);
- }
-
- /**
- * @return The name of this property in the FHIR Specification
- */
- public String getName() {
- return name;
- }
-
- /**
- * @return The stated type in the FHIR specification
- */
- public String getTypeCode() {
- return typeCode;
- }
-
- /**
- * @return The definition of this element in the FHIR spec
- */
- public String getDefinition() {
- return definition;
- }
-
- /**
- * @return the minimum cardinality for this element
- */
- public int getMinCardinality() {
- return minCardinality;
- }
-
- /**
- * @return the maximum cardinality for this element
- */
- public int getMaxCardinality() {
- return maxCardinality;
- }
-
- /**
- * @return the actual values - will only be 1 unless maximum cardinality == MAX_INT
- */
- public List getValues() {
- return values;
- }
-
- public boolean hasValues() {
- for (Base e : getValues())
- if (e != null)
- return true;
- return false;
- }
-
- public StructureDefinition getStructure() {
- return structure;
- }
-
- public void setStructure(StructureDefinition structure) {
- this.structure = structure;
- }
-
-
-
-}
+package org.hl7.fhir.dstu3.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A child element or property defined by the FHIR specification
+ * This class is defined as a helper class when iterating the
+ * children of an element in a generic fashion
+ *
+ * At present, iteration is only based on the specification, but
+ * this may be changed to allow profile based expression at a
+ * later date
+ *
+ * note: there's no point in creating one of these classes outside this package
+ */
+public class Property {
+
+ /**
+ * The name of the property as found in the FHIR specification
+ */
+ private String name;
+
+ /**
+ * The type of the property as specified in the FHIR specification (e.g. type|type|Reference(Name|Name)
+ */
+ private String typeCode;
+
+ /**
+ * The formal definition of the element given in the FHIR specification
+ */
+ private String definition;
+
+ /**
+ * The minimum allowed cardinality - 0 or 1 when based on the specification
+ */
+ private int minCardinality;
+
+ /**
+ * The maximum allowed cardinality - 1 or MAX_INT when based on the specification
+ */
+ private int maxCardinality;
+
+ /**
+ * The actual elements that exist on this instance
+ */
+ private List values = new ArrayList();
+
+ /**
+ * For run time, if/once a property is hooked up to it's definition
+ */
+ private StructureDefinition structure;
+
+ /**
+ * Internal constructor
+ */
+ public Property(String name, String typeCode, String definition, int minCardinality, int maxCardinality, Base value) {
+ super();
+ this.name = name;
+ this.typeCode = typeCode;
+ this.definition = definition;
+ this.minCardinality = minCardinality;
+ this.maxCardinality = maxCardinality;
+ this.values.add(value);
+ }
+
+ /**
+ * Internal constructor
+ */
+ public Property(String name, String typeCode, String definition, int minCardinality, int maxCardinality, List extends Base> values) {
+ super();
+ this.name = name;
+ this.typeCode = typeCode;
+ this.definition = definition;
+ this.minCardinality = minCardinality;
+ this.maxCardinality = maxCardinality;
+ if (values != null)
+ this.values.addAll(values);
+ }
+
+ /**
+ * @return The name of this property in the FHIR Specification
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The stated type in the FHIR specification
+ */
+ public String getTypeCode() {
+ return typeCode;
+ }
+
+ /**
+ * @return The definition of this element in the FHIR spec
+ */
+ public String getDefinition() {
+ return definition;
+ }
+
+ /**
+ * @return the minimum cardinality for this element
+ */
+ public int getMinCardinality() {
+ return minCardinality;
+ }
+
+ /**
+ * @return the maximum cardinality for this element
+ */
+ public int getMaxCardinality() {
+ return maxCardinality;
+ }
+
+ /**
+ * @return the actual values - will only be 1 unless maximum cardinality == MAX_INT
+ */
+ public List getValues() {
+ return values;
+ }
+
+ public boolean hasValues() {
+ for (Base e : getValues())
+ if (e != null)
+ return true;
+ return false;
+ }
+
+ public StructureDefinition getStructure() {
+ return structure;
+ }
+
+ public void setStructure(StructureDefinition structure) {
+ this.structure = structure;
+ }
+
+ public boolean isList() {
+ return maxCardinality > 1;
+ }
+
+
+}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java
new file mode 100644
index 00000000000..fd2b13d2857
--- /dev/null
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/GraphQLEngine.java
@@ -0,0 +1,846 @@
+package org.hl7.fhir.dstu3.utils;
+
+import org.hl7.fhir.dstu3.model.*;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.dstu3.context.IWorkerContext;
+import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
+import org.hl7.fhir.dstu3.model.Bundle.BundleLinkComponent;
+import org.hl7.fhir.utilities.Utilities;
+import org.hl7.fhir.utilities.graphql.*;
+import org.hl7.fhir.utilities.graphql.Operation.OperationType;
+import org.hl7.fhir.utilities.graphql.Package;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class GraphQLEngine {
+
+ public class SearchEdge extends Base {
+
+ private BundleEntryComponent be;
+ private String type;
+
+ public SearchEdge(String type, BundleEntryComponent be) {
+ this.type = type;
+ this.be = be;
+ }
+ @Override
+ public String fhirType() {
+ return type;
+ }
+
+ @Override
+ protected void listChildren(List result) {
+ throw new Error("Not Implemented");
+ }
+
+ @Override
+ public String getIdBase() {
+ throw new Error("Not Implemented");
+ }
+
+ @Override
+ public void setIdBase(String value) {
+ throw new Error("Not Implemented");
+ }
+
+// @Override
+// public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
+// switch (_hash) {
+// case 3357091: /*mode*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasMode() ? be.getSearch().getModeElement() : null);
+// case 109264530: /*score*/ return new Property(_name, "string", "n/a", 0, 1, be.getSearch().hasScore() ? be.getSearch().getScoreElement() : null);
+// case -341064690: /*resource*/ return new Property(_name, "resource", "n/a", 0, 1, be.hasResource() ? be.getResource() : null);
+// default: return super.getNamedProperty(_hash, _name, _checkValid);
+// }
+// }
+ }
+
+ public class SearchWrapper extends Base {
+
+ private Bundle bnd;
+ private String type;
+ private Map map;
+
+ public SearchWrapper(String type, Bundle bnd) throws FHIRException {
+ this.type = type;
+ this.bnd = bnd;
+ for (BundleLinkComponent bl : bnd.getLink())
+ if (bl.getRelation().equals("self"))
+ map = parseURL(bl.getUrl());
+ }
+
+ @Override
+ public String fhirType() {
+ return type;
+ }
+
+ @Override
+ protected void listChildren(List result) {
+ throw new Error("Not Implemented");
+ }
+
+ @Override
+ public String getIdBase() {
+ throw new Error("Not Implemented");
+ }
+
+ @Override
+ public void setIdBase(String value) {
+ throw new Error("Not Implemented");
+ }
+
+// @Override
+// public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
+// switch (_hash) {
+// case 97440432: /*first*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
+// case -1273775369: /*previous*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
+// case 3377907: /*next*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
+// case 3314326: /*last*/ return new Property(_name, "string", "n/a", 0, 1, extractLink(_name));
+// case 94851343: /*count*/ return new Property(_name, "integer", "n/a", 0, 1, bnd.getTotalElement());
+// case -1019779949:/*offset*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("search-offset"));
+// case 860381968: /*pagesize*/ return new Property(_name, "integer", "n/a", 0, 1, extractParam("_count"));
+// case 96356950: /*edges*/ return new Property(_name, "edge", "n/a", 0, Integer.MAX_VALUE, getEdges());
+// default: return super.getNamedProperty(_hash, _name, _checkValid);
+// }
+// }
+
+ private List getEdges() {
+ List list = new ArrayList<>();
+ for (BundleEntryComponent be : bnd.getEntry())
+ list.add(new SearchEdge(type.substring(0, type.length()-10)+"Edge", be));
+ return list;
+ }
+
+ private Base extractParam(String name) throws FHIRException {
+ return map != null ? new IntegerType(map.get(name)) : null;
+ }
+
+ private Map parseURL(String url) throws FHIRException {
+ try {
+ Map map = new HashMap();
+ String[] pairs = url.split("&");
+ for (String pair : pairs) {
+ int idx = pair.indexOf("=");
+ String key;
+ key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
+ String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
+ map.put(key, value);
+ }
+ return map;
+ } catch (UnsupportedEncodingException e) {
+ throw new FHIRException(e);
+ }
+ }
+
+ private Base extractLink(String _name) throws FHIRException {
+ for (BundleLinkComponent bl : bnd.getLink()) {
+ if (bl.getRelation().equals(_name)) {
+ Map map = parseURL(bl.getUrl());
+ return new StringType(map.get("search-id")+':'+map.get("search-offset"));
+ }
+ }
+ return null;
+ }
+
+ }
+
+
+ private IWorkerContext context;
+
+ public GraphQLEngine(IWorkerContext context) {
+ super();
+ this.context = context;
+ }
+
+ /**
+ * for the host to pass context into and get back on the reference resolution interface
+ */
+ private Object appInfo;
+
+ /**
+ * the focus resource - if (there instanceof one. if (there isn"t,) there instanceof no focus
+ */
+ private Resource focus;
+
+ /**
+ * The package that describes the graphQL to be executed, operation name, and variables
+ */
+ private Package graphQL;
+
+ /**
+ * where the output from executing the query instanceof going to go
+ */
+ private ObjectValue output;
+
+ /**
+ * Application provided reference resolution services
+ */
+ private IGraphQLStorageServices services;
+
+ // internal stuff
+ private Map workingVariables = new HashMap();
+
+ public void execute() throws EGraphEngine, EGraphQLException, FHIRException {
+ if (graphQL == null)
+ throw new EGraphEngine("Unable to process graphql - graphql document missing");
+
+ output = new ObjectValue();
+
+ Operation op = null;
+ // todo: initial conditions
+ if (!Utilities.noString(graphQL.getOperationName())) {
+ op = graphQL.getDocument().operation(graphQL.getOperationName());
+ if (op == null)
+ throw new EGraphEngine("Unable to find operation \""+graphQL.getOperationName()+"\"");
+ } else if ((graphQL.getDocument().getOperations().size() == 1))
+ op = graphQL.getDocument().getOperations().get(0);
+ else
+ throw new EGraphQLException("No operation name provided, so expected to find a single operation");
+
+ if (op.getOperationType() == OperationType.qglotMutation)
+ throw new EGraphQLException("Mutation operations are not supported (yet)");
+
+ checkNoDirectives(op.getDirectives());
+ processVariables(op);
+ if (focus == null)
+ processSearch(output, op.getSelectionSet());
+ else
+ processObject(focus, focus, output, op.getSelectionSet());
+ }
+
+ private boolean checkBooleanDirective(Directive dir) throws EGraphQLException {
+ if (dir.getArguments().size() != 1)
+ throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\"");
+ if (!dir.getArguments().get(0).getName().equals("if"))
+ throw new EGraphQLException("Unable to process @"+dir.getName()+": expected a single argument \"if\"");
+ List vl = resolveValues(dir.getArguments().get(0), 1);
+ return vl.get(0).toString().equals("true");
+ }
+
+ private boolean checkDirectives(List directives) throws EGraphQLException {
+ Directive skip = null;
+ Directive include = null;
+ for (Directive dir : directives) {
+ if (dir.getName().equals("skip")) {
+ if ((skip == null))
+ skip = dir;
+ else
+ throw new EGraphQLException("Duplicate @skip directives");
+ } else if (dir.getName().equals("include")) {
+ if ((include == null))
+ include = dir;
+ else
+ throw new EGraphQLException("Duplicate @include directives");
+ }
+ else
+ throw new EGraphQLException("Directive \""+dir.getName()+"\" instanceof not recognised");
+ }
+ if ((skip != null && include != null))
+ throw new EGraphQLException("Cannot mix @skip and @include directives");
+ if (skip != null)
+ return !checkBooleanDirective(skip);
+ else if (include != null)
+ return checkBooleanDirective(include);
+ else
+ return true;
+ }
+
+ private void checkNoDirectives(List directives) {
+
+ }
+
+ private boolean targetTypeOk(List arguments, Resource dest) throws EGraphQLException {
+ List list = new ArrayList();
+ for (Argument arg : arguments) {
+ if ((arg.getName().equals("type"))) {
+ List vl = resolveValues(arg);
+ for (Value v : vl)
+ list.add(v.toString());
+ }
+ }
+ if (list.size() == 0)
+ return true;
+ else
+ return list.indexOf(dest.fhirType()) > -1;
+ }
+
+ private boolean hasExtensions(Base obj) {
+ if (obj instanceof BackboneElement)
+ return ((BackboneElement) obj).getExtension().size() > 0 || ((BackboneElement) obj).getModifierExtension().size() > 0;
+ else if (obj instanceof DomainResource)
+ return ((DomainResource)obj).getExtension().size() > 0 || ((DomainResource)obj).getModifierExtension().size() > 0;
+ else if (obj instanceof Element)
+ return ((Element)obj).getExtension().size() > 0;
+ else
+ return false;
+ }
+
+ private boolean passesExtensionMode(Base obj, boolean extensionMode) {
+ if (!obj.isPrimitive())
+ return !extensionMode;
+ else if (extensionMode)
+ return !Utilities.noString(obj.getIdBase()) || hasExtensions(obj);
+ else
+ return obj.primitiveValue() != "";
+ }
+
+ private List filter(Resource context, Property prop, List arguments, List values, boolean extensionMode) throws FHIRException, EGraphQLException {
+ List result = new ArrayList();
+ if (values.size() > 0) {
+ StringBuilder fp = new StringBuilder();
+ for (Argument arg : arguments) {
+ List vl = resolveValues(arg);
+ if ((vl.size() != 1))
+ throw new EGraphQLException("Incorrect number of arguments");
+ if (values.get(0).isPrimitive())
+ throw new EGraphQLException("Attempt to use a filter ("+arg.getName()+") on a primtive type ("+prop.getTypeCode()+")");
+ if ((arg.getName().equals("fhirpath")))
+ fp.append(" and "+vl.get(0).toString());
+ else {
+ Property p = values.get(0).getNamedProperty(arg.getName());
+ if (p == null)
+ throw new EGraphQLException("Attempt to use an unknown filter ("+arg.getName()+") on a type ("+prop.getTypeCode()+")");
+ fp.append(" and "+arg.getName()+" = '"+vl.get(0).toString()+"'");
+ }
+ }
+ if (fp.length() == 0)
+ for (Base v : values) {
+ if (passesExtensionMode(v, extensionMode))
+ result.add(v);
+ } else {
+ FHIRPathEngine fpe = new FHIRPathEngine(this.context);
+ ExpressionNode node = fpe.parse(fp.toString().substring(5));
+ for (Base v : values)
+ if (passesExtensionMode(v, extensionMode) && fpe.evaluateToBoolean(null, context, v, node))
+ result.add(v);
+ }
+ }
+ return result;
+ }
+
+ private List filterResources(Argument fhirpath, Bundle bnd) throws EGraphQLException, FHIRException {
+ List result = new ArrayList();
+ if (bnd.getEntry().size() > 0) {
+ if ((fhirpath == null))
+ for (BundleEntryComponent be : bnd.getEntry())
+ result.add(be.getResource());
+ else {
+ FHIRPathEngine fpe = new FHIRPathEngine(context);
+ ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
+ for (BundleEntryComponent be : bnd.getEntry())
+ if (fpe.evaluateToBoolean(null, be.getResource(), be.getResource(), node))
+ result.add(be.getResource());
+ }
+ }
+ return result;
+ }
+
+ private List filterResources(Argument fhirpath, List list) throws EGraphQLException, FHIRException {
+ List result = new ArrayList();
+ if (list.size() > 0) {
+ if ((fhirpath == null))
+ for (Resource v : list)
+ result.add(v);
+ else {
+ FHIRPathEngine fpe = new FHIRPathEngine(context);
+ ExpressionNode node = fpe.parse(getSingleValue(fhirpath));
+ for (Resource v : list)
+ if (fpe.evaluateToBoolean(null, v, v, node))
+ result.add(v);
+ }
+ }
+ return result;
+ }
+
+ private boolean hasArgument(List arguments, String name, String value) {
+ for (Argument arg : arguments)
+ if ((arg.getName().equals(name)) && arg.hasValue(value))
+ return true;
+ return false;
+ }
+
+ private void processValues(Resource context, Selection sel, Property prop, ObjectValue target, List values, boolean extensionMode) throws EGraphQLException, FHIRException {
+ Argument arg = target.addField(sel.getField().getAlias(), prop.isList());
+ for (Base value : values) {
+ if (value.isPrimitive() && !extensionMode) {
+ if (!sel.getField().getSelectionSet().isEmpty())
+ throw new EGraphQLException("Encountered a selection set on a scalar field type");
+ processPrimitive(arg, value);
+ } else {
+ if (sel.getField().getSelectionSet().isEmpty())
+ throw new EGraphQLException("No Fields selected on a complex object");
+ ObjectValue n = new ObjectValue();
+ arg.addValue(n);
+ processObject(context, value, n, sel.getField().getSelectionSet());
+ }
+ }
+ }
+
+ private void processVariables(Operation op) throws EGraphQLException {
+ for (Variable varRef : op.getVariables()) {
+ Argument varDef = null;
+ for (Argument v : graphQL.getVariables())
+ if (v.getName().equals(varRef.getName()))
+ varDef = v;
+ if (varDef != null)
+ workingVariables.put(varRef.getName(), varDef); // todo: check type?
+ else if (varRef.getDefaultValue() != null)
+ workingVariables.put(varRef.getName(), new Argument(varRef.getName(), varRef.getDefaultValue()));
+ else
+ throw new EGraphQLException("No value found for variable ");
+ }
+ }
+
+ private boolean isPrimitive(String typename) {
+ return Utilities.existsInList(typename, "boolean", "integer", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt");
+ }
+
+ private boolean isResourceName(String name, String suffix) {
+ if (!name.endsWith(suffix))
+ return false;
+ name = name.substring(0, name.length()-suffix.length());
+ return context.getResourceNames().contains(name);
+ }
+
+ private void processObject(Resource context, Base source, ObjectValue target, List selection) throws EGraphQLException, FHIRException {
+ for (Selection sel : selection) {
+ if (sel.getField() != null) {
+ if (checkDirectives(sel.getField().getDirectives())) {
+ Property prop = source.getNamedProperty(sel.getField().getName());
+ if ((prop == null) && sel.getField().getName().startsWith("_"))
+ prop = source.getNamedProperty(sel.getField().getName().substring(1));
+ if (prop == null) {
+ if ((sel.getField().getName().equals("resourceType") && source instanceof Resource))
+ target.addField("resourceType", false).addValue(new StringValue(source.fhirType()));
+ else if ((sel.getField().getName().equals("resource") && source.fhirType().equals("Reference")))
+ processReference(context, source, sel.getField(), target);
+ else if (isResourceName(sel.getField().getName(), "List") && (source instanceof Resource))
+ processReverseReferenceList((Resource) source, sel.getField(), target);
+ else if (isResourceName(sel.getField().getName(), "Connection") && (source instanceof Resource))
+ processReverseReferenceSearch((Resource) source, sel.getField(), target);
+ else
+ throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
+ } else {
+ if (!isPrimitive(prop.getTypeCode()) && sel.getField().getName().startsWith("_"))
+ throw new EGraphQLException("Unknown property "+sel.getField().getName()+" on "+source.fhirType());
+
+ List vl = filter(context, prop, sel.getField().getArguments(), prop.getValues(), sel.getField().getName().startsWith("_"));
+ if (!vl.isEmpty())
+ processValues(context, sel, prop, target, vl, sel.getField().getName().startsWith("_"));
+ }
+ }
+ } else if (sel.getInlineFragment() != null) {
+ if (checkDirectives(sel.getInlineFragment().getDirectives())) {
+ if (Utilities.noString(sel.getInlineFragment().getTypeCondition()))
+ throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid?
+ if (source.fhirType().equals(sel.getInlineFragment().getTypeCondition()))
+ processObject(context, source, target, sel.getInlineFragment().getSelectionSet());
+ }
+ } else if (checkDirectives(sel.getFragmentSpread().getDirectives())) {
+ Fragment fragment = graphQL.getDocument().fragment(sel.getFragmentSpread().getName());
+ if (fragment == null)
+ throw new EGraphQLException("Unable to resolve fragment "+sel.getFragmentSpread().getName());
+
+ if (Utilities.noString(fragment.getTypeCondition()))
+ throw new EGraphQLException("Not done yet - inline fragment with no type condition"); // cause why? why instanceof it even valid?
+ if (source.fhirType().equals(fragment.getTypeCondition()))
+ processObject(context, source, target, fragment.getSelectionSet());
+ }
+ }
+ }
+
+ private void processPrimitive(Argument arg, Base value) {
+ String s = value.fhirType();
+ if (s.equals("integer") || s.equals("decimal") || s.equals("unsignedInt") || s.equals("positiveInt"))
+ arg.addValue(new NumberValue(value.primitiveValue()));
+ else if (s.equals("boolean"))
+ arg.addValue(new NameValue(value.primitiveValue()));
+ else
+ arg.addValue(new StringValue(value.primitiveValue()));
+ }
+
+ private void processReference(Resource context, Base source, Field field, ObjectValue target) throws EGraphQLException, FHIRException {
+ if (!(source instanceof Reference))
+ throw new EGraphQLException("Not done yet");
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+
+ Reference ref = (Reference) source;
+ ReferenceResolution res = services.lookup(appInfo, context, ref);
+ if (res != null) {
+ if (targetTypeOk(field.getArguments(), res.getTarget())) {
+ Argument arg = target.addField(field.getAlias(), false);
+ ObjectValue obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(res.getTargetContext(), res.getTarget(), obj, field.getSelectionSet());
+ }
+ }
+ else if (!hasArgument(field.getArguments(), "optional", "true"))
+ throw new EGraphQLException("Unable to resolve reference to "+ref.getReference());
+ }
+
+ private void processReverseReferenceList(Resource source, Field field, ObjectValue target) throws EGraphQLException, FHIRException {
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+ List list = new ArrayList();
+ List params = new ArrayList();
+ Argument parg = null;
+ for (Argument a : field.getArguments())
+ if (!(a.getName().equals("_reference")))
+ params.add(a);
+ else if ((parg == null))
+ parg = a;
+ else
+ throw new EGraphQLException("Duplicate parameter _reference");
+ if (parg == null)
+ throw new EGraphQLException("Missing parameter _reference");
+ Argument arg = new Argument();
+ params.add(arg);
+ arg.setName(getSingleValue(parg));
+ arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
+ services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), params, list);
+ arg = null;
+ ObjectValue obj = null;
+
+ List vl = filterResources(field.argument("fhirpath"), list);
+ if (!vl.isEmpty()) {
+ arg = target.addField(field.getAlias(), true);
+ for (Resource v : vl) {
+ obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(v, v, obj, field.getSelectionSet());
+ }
+ }
+ }
+
+ private void processReverseReferenceSearch(Resource source, Field field, ObjectValue target) throws EGraphQLException, FHIRException {
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+ List params = new ArrayList();
+ Argument parg = null;
+ for (Argument a : field.getArguments())
+ if (!(a.getName().equals("_reference")))
+ params.add(a);
+ else if ((parg == null))
+ parg = a;
+ else
+ throw new EGraphQLException("Duplicate parameter _reference");
+ if (parg == null)
+ throw new EGraphQLException("Missing parameter _reference");
+ Argument arg = new Argument();
+ params.add(arg);
+ arg.setName(getSingleValue(parg));
+ arg.addValue(new StringValue(source.fhirType()+"/"+source.getId()));
+ Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
+ Base bndWrapper = new SearchWrapper(field.getName(), bnd);
+ arg = target.addField(field.getAlias(), false);
+ ObjectValue obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(null, bndWrapper, obj, field.getSelectionSet());
+ }
+
+ private void processSearch(ObjectValue target, List selection) throws EGraphQLException, FHIRException {
+ for (Selection sel : selection) {
+ if ((sel.getField() == null))
+ throw new EGraphQLException("Only field selections are allowed in this context");
+ checkNoDirectives(sel.getField().getDirectives());
+
+ if ((isResourceName(sel.getField().getName(), "")))
+ processSearchSingle(target, sel.getField());
+ else if ((isResourceName(sel.getField().getName(), "List")))
+ processSearchSimple(target, sel.getField());
+ else if ((isResourceName(sel.getField().getName(), "Connection")))
+ processSearchFull(target, sel.getField());
+ }
+ }
+
+ private void processSearchSingle(ObjectValue target, Field field) throws EGraphQLException, FHIRException {
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+ String id = "";
+ for (Argument arg : field.getArguments())
+ if ((arg.getName().equals("id")))
+ id = getSingleValue(arg);
+ else
+ throw new EGraphQLException("Unknown/invalid parameter "+arg.getName());
+ if (Utilities.noString(id))
+ throw new EGraphQLException("No id found");
+ Resource res = services.lookup(appInfo, field.getName(), id);
+ if (res == null)
+ throw new EGraphQLException("Resource "+field.getName()+"/"+id+" not found");
+ Argument arg = target.addField(field.getAlias(), false);
+ ObjectValue obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(res, res, obj, field.getSelectionSet());
+ }
+
+ private void processSearchSimple(ObjectValue target, Field field) throws EGraphQLException, FHIRException {
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+ List list = new ArrayList();
+ services.listResources(appInfo, field.getName().substring(0, field.getName().length() - 4), field.getArguments(), list);
+ Argument arg = null;
+ ObjectValue obj = null;
+
+ List vl = filterResources(field.argument("fhirpath"), list);
+ if (!vl.isEmpty()) {
+ arg = target.addField(field.getAlias(), true);
+ for (Resource v : vl) {
+ obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(v, v, obj, field.getSelectionSet());
+ }
+ }
+ }
+
+ private void processSearchFull(ObjectValue target, Field field) throws EGraphQLException, FHIRException {
+ if (services == null)
+ throw new EGraphQLException("Resource Referencing services not provided");
+ List params = new ArrayList();
+ Argument carg = null;
+ for ( Argument arg : field.getArguments())
+ if (arg.getName().equals("cursor"))
+ carg = arg;
+ else
+ params.add(arg);
+ if ((carg != null)) {
+ params.clear();;
+ String[] parts = getSingleValue(carg).split(":");
+ params.add(new Argument("search-id", new StringValue(parts[0])));
+ params.add(new Argument("search-offset", new StringValue(parts[1])));
+ }
+
+ Bundle bnd = services.search(appInfo, field.getName().substring(0, field.getName().length()-10), params);
+ SearchWrapper bndWrapper = new SearchWrapper(field.getName(), bnd);
+ Argument arg = target.addField(field.getAlias(), false);
+ ObjectValue obj = new ObjectValue();
+ arg.addValue(obj);
+ processObject(null, bndWrapper, obj, field.getSelectionSet());
+ }
+
+ private String getSingleValue(Argument arg) throws EGraphQLException {
+ List vl = resolveValues(arg, 1);
+ if (vl.size() == 0)
+ return "";
+ return vl.get(0).toString();
+ }
+
+ private List resolveValues(Argument arg) throws EGraphQLException {
+ return resolveValues(arg, -1, "");
+ }
+
+ private List resolveValues(Argument arg, int max) throws EGraphQLException {
+ return resolveValues(arg, max, "");
+ }
+
+ private List resolveValues(Argument arg, int max, String vars) throws EGraphQLException {
+ List result = new ArrayList();
+ for (Value v : arg.getValues()) {
+ if (! (v instanceof VariableValue))
+ result.add(v);
+ else {
+ if (vars.contains(":"+v.toString()+":"))
+ throw new EGraphQLException("Recursive reference to variable "+v.toString());
+ Argument a = workingVariables.get(v.toString());
+ if (a == null)
+ throw new EGraphQLException("No value found for variable \""+v.toString()+"\" in \""+arg.getName()+"\"");
+ List vl = resolveValues(a, -1, vars+":"+v.toString()+":");
+ result.addAll(vl);
+ }
+ }
+ if ((max != -1 && result.size() > max))
+ throw new EGraphQLException("Only "+Integer.toString(max)+" values are allowed for \""+arg.getName()+"\", but "+Integer.toString(result.size())+" enoucntered");
+ return result;
+ }
+
+
+
+
+ public Object getAppInfo() {
+ return appInfo;
+ }
+
+ public void setAppInfo(Object appInfo) {
+ this.appInfo = appInfo;
+ }
+
+ public Resource getFocus() {
+ return focus;
+ }
+
+ public void setFocus(Resource focus) {
+ this.focus = focus;
+ }
+
+ public Package getGraphQL() {
+ return graphQL;
+ }
+
+ public void setGraphQL(Package graphQL) {
+ this.graphQL = graphQL;
+ }
+
+ public ObjectValue getOutput() {
+ return output;
+ }
+
+ public IGraphQLStorageServices getServices() {
+ return services;
+ }
+
+ public void setServices(IGraphQLStorageServices services) {
+ this.services = services;
+ }
+
+
+ //
+//{ GraphQLSearchWrapper }
+//
+//constructor GraphQLSearchWrapper.Create(bundle : Bundle);
+//var
+// s : String;
+//{
+// inherited Create;
+// FBundle = bundle;
+// s = bundle_List.Matches["self"];
+// FParseMap = TParseMap.create(s.Substring(s.IndexOf("?")+1));
+//}
+//
+//destructor GraphQLSearchWrapper.Destroy;
+//{
+// FParseMap.free;
+// FBundle.Free;
+// inherited;
+//}
+//
+//function GraphQLSearchWrapper.extractLink(name: String): String;
+//var
+// s : String;
+// pm : TParseMap;
+//{
+// s = FBundle_List.Matches[name];
+// if (s == "")
+// result = null
+// else
+// {
+// pm = TParseMap.create(s.Substring(s.IndexOf("?")+1));
+// try
+// result = String.Create(pm.GetVar("search-id")+":"+pm.GetVar("search-offset"));
+// finally
+// pm.Free;
+// }
+// }
+//}
+//
+//function GraphQLSearchWrapper.extractParam(name: String; int : boolean): Base;
+//var
+// s : String;
+//{
+// s = FParseMap.GetVar(name);
+// if (s == "")
+// result = null
+// else if (int)
+// result = Integer.Create(s)
+// else
+// result = String.Create(s);
+//}
+//
+//function GraphQLSearchWrapper.fhirType(): String;
+//{
+// result = "*Connection";
+//}
+//
+// // http://test.fhir.org/r3/Patient?_format==text/xhtml&search-id==77c97e03-8a6c-415f-a63d-11c80cf73f&&active==true&_sort==_id&search-offset==50&_count==50
+//
+//function GraphQLSearchWrapper.getPropertyValue(propName: string): Property;
+//var
+// list : List;
+// be : BundleEntry;
+//{
+// if (propName == "first")
+// result = Property.Create(self, propname, "string", false, String, extractLink("first"))
+// else if (propName == "previous")
+// result = Property.Create(self, propname, "string", false, String, extractLink("previous"))
+// else if (propName == "next")
+// result = Property.Create(self, propname, "string", false, String, extractLink("next"))
+// else if (propName == "last")
+// result = Property.Create(self, propname, "string", false, String, extractLink("last"))
+// else if (propName == "count")
+// result = Property.Create(self, propname, "integer", false, String, FBundle.totalElement)
+// else if (propName == "offset")
+// result = Property.Create(self, propname, "integer", false, Integer, extractParam("search-offset", true))
+// else if (propName == "pagesize")
+// result = Property.Create(self, propname, "integer", false, Integer, extractParam("_count", true))
+// else if (propName == "edges")
+// {
+// list = ArrayList();
+// try
+// for be in FBundle.getEntry() do
+// list.add(GraphQLSearchEdge.create(be));
+// result = Property.Create(self, propname, "integer", true, Integer, List(list));
+// finally
+// list.Free;
+// }
+// }
+// else
+// result = null;
+//}
+//
+//private void GraphQLSearchWrapper.SetBundle(const Value: Bundle);
+//{
+// FBundle.Free;
+// FBundle = Value;
+//}
+//
+//{ GraphQLSearchEdge }
+//
+//constructor GraphQLSearchEdge.Create(entry: BundleEntry);
+//{
+// inherited Create;
+// FEntry = entry;
+//}
+//
+//destructor GraphQLSearchEdge.Destroy;
+//{
+// FEntry.Free;
+// inherited;
+//}
+//
+//function GraphQLSearchEdge.fhirType(): String;
+//{
+// result = "*Edge";
+//}
+//
+//function GraphQLSearchEdge.getPropertyValue(propName: string): Property;
+//{
+// if (propName == "mode")
+// {
+// if (FEntry.search != null)
+// result = Property.Create(self, propname, "code", false, Enum, FEntry.search.modeElement)
+// else
+// result = Property.Create(self, propname, "code", false, Enum, Base(null));
+// }
+// else if (propName == "score")
+// {
+// if (FEntry.search != null)
+// result = Property.Create(self, propname, "decimal", false, Decimal, FEntry.search.scoreElement)
+// else
+// result = Property.Create(self, propname, "decimal", false, Decimal, Base(null));
+// }
+// else if (propName == "resource")
+// result = Property.Create(self, propname, "resource", false, Resource, FEntry.getResource())
+// else
+// result = null;
+//}
+//
+//private void GraphQLSearchEdge.SetEntry(const Value: BundleEntry);
+//{
+// FEntry.Free;
+// FEntry = value;
+//}
+//
+}
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java
new file mode 100644
index 00000000000..8c4a7d53892
--- /dev/null
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/GraphQLDstu3ProviderTest.java
@@ -0,0 +1,280 @@
+package ca.uhn.fhir.rest.server;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.annotation.OptionalParam;
+import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.param.TokenAndListParam;
+import ca.uhn.fhir.util.PortUtil;
+import ca.uhn.fhir.util.TestUtil;
+import ca.uhn.fhir.util.UrlUtil;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.hl7.fhir.dstu3.model.*;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.dstu3.hapi.rest.server.GraphQLProviderDstu3;
+import org.hl7.fhir.utilities.graphql.Argument;
+import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
+import org.hl7.fhir.utilities.graphql.ReferenceResolution;
+import org.junit.*;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class GraphQLDstu3ProviderTest {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GraphQLDstu3ProviderTest.class);
+ private static CloseableHttpClient ourClient;
+ private static FhirContext ourCtx = FhirContext.forDstu3();
+ private static int ourPort;
+ private static Server ourServer;
+
+ @AfterClass
+ public static void afterClassClearContext() throws Exception {
+ ourServer.stop();
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ourPort = PortUtil.findFreePort();
+ ourServer = new Server(ourPort);
+
+ ServletHandler proxyHandler = new ServletHandler();
+ RestfulServer servlet = new RestfulServer(ourCtx);
+ servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
+ servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
+
+ servlet.registerProvider(new DummyPatientResourceProvider());
+ MyStorageServices storageServices = new MyStorageServices();
+ servlet.registerProvider(new GraphQLProviderDstu3(storageServices));
+ ServletHolder servletHolder = new ServletHolder(servlet);
+ proxyHandler.addServletWithMapping(servletHolder, "/*");
+ ourServer.setHandler(proxyHandler);
+ ourServer.start();
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setConnectionManager(connectionManager);
+ ourClient = builder.build();
+
+ }
+
+ @Before
+ public void before() {
+ //nothing
+ }
+
+ @Test
+ @Ignore
+ public void testGraphInstance() throws Exception {
+ String query = "{name{family,given}}";
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query));
+ CloseableHttpResponse status = ourClient.execute(httpGet);
+ try {
+ String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(responseContent);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ assertEquals("{\n" +
+ " \"name\":[{\n" +
+ " \"family\":\"FAMILY\",\n" +
+ " \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
+ " },{\n" +
+ " \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
+ " }]\n" +
+ "}", responseContent);
+ assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
+
+ } finally {
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ }
+
+ }
+
+ @Test
+ @Ignore
+ public void testGraphSystemInstance() throws Exception {
+ String query = "{Patient(id:123){id,name{given,family}}}";
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query));
+ CloseableHttpResponse status = ourClient.execute(httpGet);
+ try {
+ String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(responseContent);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ assertEquals("{\n" +
+ " \"Patient\":{\n" +
+ " \"name\":[{\n" +
+ " \"given\":[\"GIVEN1\",\"GIVEN2\"],\n" +
+ " \"family\":\"FAMILY\"\n" +
+ " },{\n" +
+ " \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
+ " }]\n" +
+ " }\n" +
+ "}", responseContent);
+ assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
+
+ } finally {
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ }
+
+ }
+
+ @Test
+ @Ignore
+ public void testGraphSystemList() throws Exception {
+ String query = "{PatientList(name:\"pet\"){name{family,given}}}";
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escape(query));
+ CloseableHttpResponse status = ourClient.execute(httpGet);
+ try {
+ String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(responseContent);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ assertEquals("{\n" +
+ " \"PatientList\":[{\n" +
+ " \"name\":[{\n" +
+ " \"family\":\"pet\",\n" +
+ " \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
+ " },{\n" +
+ " \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
+ " }]\n" +
+ " },{\n" +
+ " \"name\":[{\n" +
+ " \"given\":[\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" +
+ " }]\n" +
+ " }]\n" +
+ "}", responseContent);
+ assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
+
+ } finally {
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ }
+
+ }
+
+ @Test
+ @Ignore
+ public void testGraphInstanceWithFhirpath() throws Exception {
+ String query = "{name(fhirpath:\"family.exists()\"){text,given,family}}";
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape(query));
+ CloseableHttpResponse status = ourClient.execute(httpGet);
+ try {
+ String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(responseContent);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+
+ assertEquals("{\n" +
+ " \"name\":[{\n" +
+ " \"given\":[\"GIVEN1\",\"GIVEN2\"],\n" +
+ " \"family\":\"FAMILY\"\n" +
+ " }]\n" +
+ "}", responseContent);
+ assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
+
+ } finally {
+ IOUtils.closeQuietly(status.getEntity().getContent());
+ }
+
+ }
+
+ public static class DummyPatientResourceProvider implements IResourceProvider {
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Search()
+ public List search(
+ @OptionalParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
+ ArrayList retVal = new ArrayList<>();
+
+ for (int i = 0; i < 200; i++) {
+ Patient patient = new Patient();
+ patient.addName(new HumanName().setFamily("FAMILY"));
+ patient.getIdElement().setValue("Patient/" + i);
+ retVal.add((Patient) patient);
+ }
+ return retVal;
+ }
+
+ }
+
+ private static class MyStorageServices implements IGraphQLStorageServices {
+ @Override
+ public ReferenceResolution lookup(Object theAppInfo, Resource theContext, Reference theReference) throws FHIRException {
+ ourLog.info("lookup from {} to {}", theContext.getIdElement().getValue(), theReference.getReference());
+ return null;
+ }
+
+ @Override
+ public Resource lookup(Object theAppInfo, String theType, String theId) throws FHIRException {
+ ourLog.info("lookup {}/{}", theType, theId);
+
+ if (theType.equals("Patient") && theId.equals("123")) {
+ Patient p = new Patient();
+ p.addName()
+ .setFamily("FAMILY")
+ .addGiven("GIVEN1")
+ .addGiven("GIVEN2");
+ p.addName()
+ .addGiven("GivenOnly1")
+ .addGiven("GivenOnly2");
+ return p;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void listResources(Object theAppInfo, String theType, List theSearchParams, List theMatches) throws FHIRException {
+ ourLog.info("listResources of {} - {}", theType, theSearchParams);
+
+ if (theSearchParams.size() == 1) {
+ String name = theSearchParams.get(0).getName();
+ if ("name".equals(name)) {
+ Patient p = new Patient();
+ p.addName()
+ .setFamily(theSearchParams.get(0).getValues().get(0).toString())
+ .addGiven("GIVEN1")
+ .addGiven("GIVEN2");
+ p.addName()
+ .addGiven("GivenOnly1")
+ .addGiven("GivenOnly2");
+ theMatches.add(p);
+
+ p = new Patient();
+ p.addName()
+ .addGiven("GivenOnlyB1")
+ .addGiven("GivenOnlyB2");
+ theMatches.add(p);
+
+ }
+ }
+ }
+
+ @Override
+ public Bundle search(Object theAppInfo, String theType, List theSearchParams) throws FHIRException {
+ ourLog.info("search on {} - {}", theType, theSearchParams);
+ return null;
+ }
+ }
+}
diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java
index b4942a81664..359c415c1e3 100644
--- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java
+++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java
@@ -23,6 +23,7 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.zip.GZIPInputStream;
+import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
@@ -105,6 +106,19 @@ public class FhirInstanceValidatorDstu3Test {
}
+ /**
+ * See #703
+ */
+ @Test
+ public void testDstu3UsesLatestDefinitions() throws IOException {
+ String input = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/bug703.json"), Charsets.UTF_8);
+
+ ValidationResult results = myVal.validateWithResult(input);
+ List outcome = logResultsAndReturnNonInformationalOnes(results);
+ assertThat(outcome, empty());
+
+ }
+
/**
* See #370
*/
diff --git a/hapi-fhir-structures-dstu3/src/test/resources/bug703.json b/hapi-fhir-structures-dstu3/src/test/resources/bug703.json
new file mode 100644
index 00000000000..6495a9fb5cf
--- /dev/null
+++ b/hapi-fhir-structures-dstu3/src/test/resources/bug703.json
@@ -0,0 +1,53 @@
+{
+ "resourceType": "MedicationRequest",
+ "id": "196356",
+ "meta": {
+ "versionId": "2",
+ "lastUpdated": "2017-08-04T15:26:29.333-04:00",
+ "profile": [
+ ]
+ },
+ "text": {
+ "status": "generated",
+ "div": "