From 0bc703107c60339031ca959190ede65cb46eff1d Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 29 Jul 2014 09:10:25 -0400 Subject: [PATCH] Add suport for composite and sorting to JPA (not yet complete) --- hapi-fhir-base/.classpath | 1 - .../org.eclipse.wst.common.component | 1 - hapi-fhir-base/src/changes/changes.xml | 5 + .../ca/uhn/fhir/context/ModelScanner.java | 30 +- .../uhn/fhir/context/RuntimeSearchParam.java | 32 +- .../api/annotation/SearchParamDefinition.java | 5 +- .../fhir/model/dstu/resource/Observation.java | 433 +++-- .../ca/uhn/fhir/model/primitive/IdDt.java | 98 +- .../uhn/fhir/model/primitive/InstantDt.java | 12 + .../fhir/rest/annotation/OptionalParam.java | 41 +- .../fhir/rest/annotation/RequiredParam.java | 44 +- .../uhn/fhir/rest/client/GenericClient.java | 126 +- .../uhn/fhir/rest/client/IGenericClient.java | 6 + .../rest/gclient/CompositeClientParam.java | 26 +- .../fhir/rest/gclient/CompositeCriterion.java | 34 + .../uhn/fhir/rest/gclient/CompositeParam.java | 4 +- .../fhir/rest/gclient/DateClientParam.java | 22 +- .../fhir/rest/gclient/ICompositeWithLeft.java | 7 + .../ca/uhn/fhir/rest/gclient/ICriterion.java | 2 +- .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 4 +- .../ca/uhn/fhir/rest/gclient/IUpdate.java | 11 + .../uhn/fhir/rest/gclient/IUpdateTyped.java | 11 + .../fhir/rest/gclient/NumberClientParam.java | 60 +- .../rest/gclient/QuantityClientParam.java | 14 +- .../fhir/rest/gclient/QuantityCriterion.java | 49 + .../rest/gclient/ReferenceClientParam.java | 16 +- .../fhir/rest/gclient/StringClientParam.java | 20 +- .../fhir/rest/gclient/StringCriterion.java | 6 +- .../fhir/rest/gclient/TokenClientParam.java | 25 +- .../uhn/fhir/rest/gclient/TokenCriterion.java | 14 +- .../ca/uhn/fhir/rest/method/BaseBinder.java | 60 + .../BaseResourceReturningMethodBinding.java | 15 +- .../rest/method/HttpPutClientInvocation.java | 4 + .../ca/uhn/fhir/rest/method/MethodUtil.java | 36 +- .../rest/method/QueryParameterAndBinder.java | 14 +- .../rest/method/QueryParameterOrBinder.java | 14 +- .../rest/method/QueryParameterTypeBinder.java | 33 +- .../uhn/fhir/rest/method/SearchParameter.java | 89 +- .../fhir/rest/method/UpdateMethodBinding.java | 48 +- .../rest/param/CompositeAndListParam.java | 43 + .../fhir/rest/param/CompositeOrListParam.java | 43 + .../uhn/fhir/rest/param/CompositeParam.java | 91 + .../ca/uhn/fhir/rest/param/DateParam.java | 9 + .../ca/uhn/fhir/rest/param/ParameterUtil.java | 70 +- .../ca/uhn/fhir/rest/param/StringParam.java | 9 +- .../uhn/fhir/rest/param/TokenOrListParam.java | 13 + .../ca/uhn/fhir/rest/param/TokenParam.java | 18 +- .../uhn/fhir/rest/server/RestfulServer.java | 16 +- .../java/ca/uhn/fhir/util/FhirTerser.java | 18 +- .../java/example/GenericClientExample.java | 30 + .../RestfulPatientResourceProviderMore.java | 19 + .../src/site/xdoc/doc_rest_client.xml | 38 +- .../src/site/xdoc/doc_rest_operations.xml | 28 + ...efaultThymeleafNarrativeGeneratorTest.java | 1 - .../ca/uhn/fhir/parser/JsonParserTest.java | 6 +- .../ca/uhn/fhir/rest/client/ClientTest.java | 27 +- .../fhir/rest/client/GenericClientTest.java | 104 + .../ca/uhn/fhir/rest/client/ITestClient.java | 7 +- .../rest/server/CompositeParameterTest.java | 178 ++ .../server/ResfulServerSelfReferenceTest.java | 142 +- .../ca/uhn/fhir/jpa/dao/FhirResourceDao.java | 1709 +++++++++-------- .../uhn/fhir/jpa/dao/SearchParameterMap.java | 15 +- .../uhn/fhir/jpa/dao/FhirResourceDaoTest.java | 992 +++++----- .../fhir-jpabase-spring-test-config.xml | 2 +- .../main/webapp/js/ClientCodeGeneratorHapi.js | 18 +- .../fhir/tinder/TinderJpaRestServerMojo.java | 9 +- .../fhir/tinder/model/SearchParameter.java | 12 + .../BaseStructureSpreadsheetParser.java | 6 +- .../resources/vm/jpa_resource_provider.vm | 14 +- .../src/main/resources/vm/resource.vm | 4 + .../org.eclipse.wst.common.component | 6 + restful-server-example/pom.xml | 40 +- 72 files changed, 3347 insertions(+), 1862 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeCriterion.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICompositeWithLeft.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdate.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityCriterion.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseBinder.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeAndListParam.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeOrListParam.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeParam.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java diff --git a/hapi-fhir-base/.classpath b/hapi-fhir-base/.classpath index 123df06e860..550e3f2ae04 100644 --- a/hapi-fhir-base/.classpath +++ b/hapi-fhir-base/.classpath @@ -6,7 +6,6 @@ - diff --git a/hapi-fhir-base/.settings/org.eclipse.wst.common.component b/hapi-fhir-base/.settings/org.eclipse.wst.common.component index 30be1c9ba76..1a94c8cf11d 100644 --- a/hapi-fhir-base/.settings/org.eclipse.wst.common.component +++ b/hapi-fhir-base/.settings/org.eclipse.wst.common.component @@ -2,6 +2,5 @@ - diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 1835cad8075..f40b7d60774 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -75,6 +75,11 @@ are encoded even if they have no value. Thanks to David Hay of Orion for reporting this! + + Fix: RESTful server deployed to a location where the URL to access it contained a + space (e.g. a WAR file with a space in the name) failed to work correctly. + Thanks to David Hay of Orion for reporting this! + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 070f6983351..01ceac3236a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -48,7 +49,6 @@ import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.ICompositeElement; import ca.uhn.fhir.model.api.IDatatype; import ca.uhn.fhir.model.api.IElement; -import ca.uhn.fhir.model.api.IExtension; import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResourceBlock; @@ -299,8 +299,6 @@ class ModelScanner { scanCompositeElementForChildren(theClass, resourceDef); } - - private String scanCodeTable(Class theCodeType, CodeTableDef theCodeTableDefinition) { return null; // TODO: implement } @@ -602,6 +600,9 @@ class ModelScanner { private void scanResourceForSearchParams(Class theClass, RuntimeResourceDefinition theResourceDef) { + Map nameToParam = new HashMap(); + Map compositeFields = new LinkedHashMap(); + for (Field nextField : theClass.getFields()) { SearchParamDefinition searchParam = nextField.getAnnotation(SearchParamDefinition.class); if (searchParam != null) { @@ -609,11 +610,34 @@ class ModelScanner { if (paramType == null) { throw new ConfigurationException("Searc param " + searchParam.name() + " has an invalid type: " + searchParam.type()); } + if(paramType==SearchParamTypeEnum.COMPOSITE) { + compositeFields.put(nextField, searchParam); + continue; + } RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType); theResourceDef.addSearchParam(param); + nameToParam.put(param.getName(), param); } } + for (Entry nextEntry : compositeFields.entrySet()) { + Field nextField = nextEntry.getKey(); + SearchParamDefinition searchParam = nextEntry.getValue(); + + List compositeOf = new ArrayList(); + for (String nextName:searchParam.compositeOf()) { + RuntimeSearchParam param = nameToParam.get(nextName); + if (param==null) { + ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", + new Object[] {theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()}); + continue; + } + compositeOf.add(param); + } + + RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), SearchParamTypeEnum.COMPOSITE, compositeOf); + theResourceDef.addSearchParam(param); + } } public Map getIdToResourceDefinition() { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index 05e463d416d..96f1ac7002f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.context; +import java.util.List; + import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; /* @@ -22,30 +24,29 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; * #L% */ - public class RuntimeSearchParam { private String myDescription; private String myName; - private String myPath; private SearchParamTypeEnum myParamType; -// private List myPathParts; + private String myPath; + private List myCompositeOf; public RuntimeSearchParam(String theName, String theDescription, String thePath, SearchParamTypeEnum theParamType) { + this(theName, theDescription, thePath, theParamType, null); + } + + public RuntimeSearchParam(String theName, String theDescription, String thePath, SearchParamTypeEnum theParamType, List theCompositeOf) { super(); myName = theName; myDescription = theDescription; - myPath=thePath; - myParamType=theParamType; -// myPathParts = Arrays.asList(thePath.split("\\.")); + myPath = thePath; + myParamType = theParamType; + myCompositeOf = theCompositeOf; } - public SearchParamTypeEnum getParamType() { - return myParamType; - } - - public String getPath() { - return myPath; + public List getCompositeOf() { + return myCompositeOf; } public String getDescription() { @@ -56,5 +57,12 @@ public class RuntimeSearchParam { return myName; } + public SearchParamTypeEnum getParamType() { + return myParamType; + } + + public String getPath() { + return myPath; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SearchParamDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SearchParamDefinition.java index 55fbfb80761..0b06da340fd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SearchParamDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SearchParamDefinition.java @@ -39,7 +39,10 @@ public @interface SearchParamDefinition { /** * If the parameter is of type "composite", this parameter lists the names of the parameters - * which this parameter is a composite of. E.g. "name-value-token" is a composite of "name" and "value-token" + * which this parameter is a composite of. E.g. "name-value-token" is a composite of "name" and "value-token". + *

+ * If the parameter is not a composite, this parameter must be empty + *

*/ String[] compositeOf() default {}; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java index dd8fa334eb3..01dff0c67d1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java @@ -16,26 +16,6 @@ package ca.uhn.fhir.model.dstu.resource; -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 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 java.util.Date; import java.util.List; @@ -63,13 +43,8 @@ import ca.uhn.fhir.model.dstu.composite.RatioDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.SampledDataDt; import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; -import ca.uhn.fhir.model.dstu.valueset.ObservationInterpretationCodesEnum; -import ca.uhn.fhir.model.dstu.valueset.ObservationRelationshipTypeEnum; -import ca.uhn.fhir.model.dstu.valueset.ObservationReliabilityEnum; import ca.uhn.fhir.model.dstu.valueset.ObservationStatusEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; -import ca.uhn.fhir.model.primitive.BoundCodeDt; -import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt; import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.InstantDt; @@ -113,7 +88,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.name
*

*/ - @SearchParamDefinition(name="name", path="Observation.name", description="The name of the observation type", type="token") + @SearchParamDefinition(name="name", path="Observation.name", description="The name of the observation type", type="token" ) public static final String SP_NAME = "name"; /** @@ -134,7 +109,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.value[x]
*

*/ - @SearchParamDefinition(name="value-quantity", path="Observation.value[x]", description="The value of the observation, if the value is a Quantity, or a SampledData (just search on the bounds of the values in sampled data)", type="quantity") + @SearchParamDefinition(name="value-quantity", path="Observation.value[x]", description="The value of the observation, if the value is a Quantity, or a SampledData (just search on the bounds of the values in sampled data)", type="quantity" ) public static final String SP_VALUE_QUANTITY = "value-quantity"; /** @@ -155,7 +130,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.value[x]
*

*/ - @SearchParamDefinition(name="value-concept", path="Observation.value[x]", description="The value of the observation, if the value is a CodeableConcept", type="token") + @SearchParamDefinition(name="value-concept", path="Observation.value[x]", description="The value of the observation, if the value is a CodeableConcept", type="token" ) public static final String SP_VALUE_CONCEPT = "value-concept"; /** @@ -176,7 +151,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.value[x]
*

*/ - @SearchParamDefinition(name="value-date", path="Observation.value[x]", description="The value of the observation, if the value is a Period", type="date") + @SearchParamDefinition(name="value-date", path="Observation.value[x]", description="The value of the observation, if the value is a Period", type="date" ) public static final String SP_VALUE_DATE = "value-date"; /** @@ -197,7 +172,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.value[x]
*

*/ - @SearchParamDefinition(name="value-string", path="Observation.value[x]", description="The value of the observation, if the value is a string, and also searches in CodeableConcept.text", type="string") + @SearchParamDefinition(name="value-string", path="Observation.value[x]", description="The value of the observation, if the value is a string, and also searches in CodeableConcept.text", type="string" ) public static final String SP_VALUE_STRING = "value-string"; /** @@ -210,27 +185,6 @@ public class Observation extends BaseResource implements IResource { */ public static final StringClientParam VALUE_STRING = new StringClientParam(SP_VALUE_STRING); - /** - * Search parameter constant for name-value-[x] - *

- * Description: Both name and one of the value parameters
- * Type: composite
- * Path: name & value-[x]
- *

- */ - @SearchParamDefinition(name="name-value-[x]", path="name & value-[x]", description="Both name and one of the value parameters", type="composite") - public static final String SP_NAME_VALUE_X = "name-value-[x]"; - - /** - * Fluent Client search parameter constant for name-value-[x] - *

- * Description: Both name and one of the value parameters
- * Type: composite
- * Path: name & value-[x]
- *

- */ - public static final CompositeClientParam NAME_VALUE_X = new CompositeClientParam(SP_NAME_VALUE_X); - /** * Search parameter constant for date *

@@ -239,7 +193,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.applies[x]
*

*/ - @SearchParamDefinition(name="date", path="Observation.applies[x]", description="Obtained date/time. If the obtained element is a period, a date that falls in the period", type="date") + @SearchParamDefinition(name="date", path="Observation.applies[x]", description="Obtained date/time. If the obtained element is a period, a date that falls in the period", type="date" ) public static final String SP_DATE = "date"; /** @@ -260,7 +214,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.status
*

*/ - @SearchParamDefinition(name="status", path="Observation.status", description="The status of the observation", type="token") + @SearchParamDefinition(name="status", path="Observation.status", description="The status of the observation", type="token" ) public static final String SP_STATUS = "status"; /** @@ -281,7 +235,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.reliability
*

*/ - @SearchParamDefinition(name="reliability", path="Observation.reliability", description="The reliability of the observation", type="token") + @SearchParamDefinition(name="reliability", path="Observation.reliability", description="The reliability of the observation", type="token" ) public static final String SP_RELIABILITY = "reliability"; /** @@ -302,7 +256,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.subject
*

*/ - @SearchParamDefinition(name="subject", path="Observation.subject", description="The subject that the observation is about", type="reference") + @SearchParamDefinition(name="subject", path="Observation.subject", description="The subject that the observation is about", type="reference" ) public static final String SP_SUBJECT = "subject"; /** @@ -329,7 +283,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.performer
*

*/ - @SearchParamDefinition(name="performer", path="Observation.performer", description="Who and/or what performed the observation", type="reference") + @SearchParamDefinition(name="performer", path="Observation.performer", description="Who and/or what performed the observation", type="reference" ) public static final String SP_PERFORMER = "performer"; /** @@ -356,7 +310,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.specimen
*

*/ - @SearchParamDefinition(name="specimen", path="Observation.specimen", description="", type="reference") + @SearchParamDefinition(name="specimen", path="Observation.specimen", description="", type="reference" ) public static final String SP_SPECIMEN = "specimen"; /** @@ -383,7 +337,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.related.type
*

*/ - @SearchParamDefinition(name="related-type", path="Observation.related.type", description="", type="token") + @SearchParamDefinition(name="related-type", path="Observation.related.type", description="", type="token" ) public static final String SP_RELATED_TYPE = "related-type"; /** @@ -404,7 +358,7 @@ public class Observation extends BaseResource implements IResource { * Path: Observation.related.target
*

*/ - @SearchParamDefinition(name="related-target", path="Observation.related.target", description="", type="reference") + @SearchParamDefinition(name="related-target", path="Observation.related.target", description="", type="reference" ) public static final String SP_RELATED_TARGET = "related-target"; /** @@ -424,25 +378,109 @@ public class Observation extends BaseResource implements IResource { public static final Include INCLUDE_RELATED_TARGET = new Include("Observation.related.target"); /** - * Search parameter constant for related + * Search parameter constant for name-value-quantity *

- * Description: Related Observations - search on related-type and related-target together
+ * Description: Both name and one of the value parameters
* Type: composite
- * Path: related-target & related-type
+ * Path: name & value-[x]
*

*/ - @SearchParamDefinition(name="related", path="related-target & related-type", description="Related Observations - search on related-type and related-target together", type="composite") - public static final String SP_RELATED = "related"; + @SearchParamDefinition(name="name-value-quantity", path="name & value-[x]", description="Both name and one of the value parameters", type="composite" , compositeOf={ "name", "value-quantity" } ) + public static final String SP_NAME_VALUE_QUANTITY = "name-value-quantity"; /** - * Fluent Client search parameter constant for related + * Fluent Client search parameter constant for name-value-quantity + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + public static final CompositeClientParam NAME_VALUE_QUANTITY = new CompositeClientParam(SP_NAME_VALUE_QUANTITY); + + /** + * Search parameter constant for name-value-concept + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + @SearchParamDefinition(name="name-value-concept", path="name & value-[x]", description="Both name and one of the value parameters", type="composite" , compositeOf={ "name", "value-concept" } ) + public static final String SP_NAME_VALUE_CONCEPT = "name-value-concept"; + + /** + * Fluent Client search parameter constant for name-value-concept + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + public static final CompositeClientParam NAME_VALUE_CONCEPT = new CompositeClientParam(SP_NAME_VALUE_CONCEPT); + + /** + * Search parameter constant for name-value-date + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + @SearchParamDefinition(name="name-value-date", path="name & value-[x]", description="Both name and one of the value parameters", type="composite" , compositeOf={ "name", "value-date" } ) + public static final String SP_NAME_VALUE_DATE = "name-value-date"; + + /** + * Fluent Client search parameter constant for name-value-date + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + public static final CompositeClientParam NAME_VALUE_DATE = new CompositeClientParam(SP_NAME_VALUE_DATE); + + /** + * Search parameter constant for name-value-string + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + @SearchParamDefinition(name="name-value-string", path="name & value-[x]", description="Both name and one of the value parameters", type="composite" , compositeOf={ "name", "value-string" } ) + public static final String SP_NAME_VALUE_STRING = "name-value-string"; + + /** + * Fluent Client search parameter constant for name-value-string + *

+ * Description: Both name and one of the value parameters
+ * Type: composite
+ * Path: name & value-[x]
+ *

+ */ + public static final CompositeClientParam NAME_VALUE_STRING = new CompositeClientParam(SP_NAME_VALUE_STRING); + + /** + * Search parameter constant for related-target-related-type *

* Description: Related Observations - search on related-type and related-target together
* Type: composite
* Path: related-target & related-type
*

*/ - public static final CompositeClientParam RELATED = new CompositeClientParam(SP_RELATED); + @SearchParamDefinition(name="related-target-related-type", path="related-target & related-type", description="Related Observations - search on related-type and related-target together", type="composite" , compositeOf={ "related-target", "related-type" } ) + public static final String SP_RELATED_TARGET_RELATED_TYPE = "related-target-related-type"; + + /** + * Fluent Client search parameter constant for related-target-related-type + *

+ * Description: Related Observations - search on related-type and related-target together
+ * Type: composite
+ * Path: related-target & related-type
+ *

+ */ + public static final CompositeClientParam RELATED_TARGET_RELATED_TYPE = new CompositeClientParam(SP_RELATED_TARGET_RELATED_TYPE); @Child(name="name", type=CodeableConceptDt.class, order=0, min=1, max=1) @@ -465,7 +503,7 @@ public class Observation extends BaseResource implements IResource { shortDefinition="High, low, normal, etc.", formalDefinition="The assessment made based on the result of the observation." ) - private BoundCodeableConceptDt myInterpretation; + private CodeableConceptDt myInterpretation; @Child(name="comments", type=StringDt.class, order=3, min=0, max=1) @Description( @@ -494,14 +532,14 @@ public class Observation extends BaseResource implements IResource { shortDefinition="registered | preliminary | final | amended +", formalDefinition="The status of the result value" ) - private BoundCodeDt myStatus; + private CodeDt myStatus; @Child(name="reliability", type=CodeDt.class, order=7, min=1, max=1) @Description( shortDefinition="ok | ongoing | early | questionable | calibrating | error +", formalDefinition="An estimate of the degree to which quality issues have impacted on the value reported" ) - private BoundCodeDt myReliability; + private CodeDt myReliability; @Child(name="bodySite", type=CodeableConceptDt.class, order=8, min=0, max=1) @Description( @@ -642,9 +680,9 @@ public class Observation extends BaseResource implements IResource { * The assessment made based on the result of the observation. *

*/ - public BoundCodeableConceptDt getInterpretation() { + public CodeableConceptDt getInterpretation() { if (myInterpretation == null) { - myInterpretation = new BoundCodeableConceptDt(ObservationInterpretationCodesEnum.VALUESET_BINDER); + myInterpretation = new CodeableConceptDt(); } return myInterpretation; } @@ -657,24 +695,11 @@ public class Observation extends BaseResource implements IResource { * The assessment made based on the result of the observation. *

*/ - public Observation setInterpretation(BoundCodeableConceptDt theValue) { + public Observation setInterpretation(CodeableConceptDt theValue) { myInterpretation = theValue; return this; } - /** - * Sets the value(s) for interpretation (High, low, normal, etc.) - * - *

- * Definition: - * The assessment made based on the result of the observation. - *

- */ - public Observation setInterpretation(ObservationInterpretationCodesEnum theValue) { - getInterpretation().setValueAsEnum(theValue); - return this; - } - /** * Gets the value(s) for comments (Comments about result). @@ -786,8 +811,8 @@ public class Observation extends BaseResource implements IResource { * *

*/ - public Observation setIssued( Date theDate, TemporalPrecisionEnum thePrecision) { - myIssued = new InstantDt(theDate, thePrecision); + public Observation setIssuedWithMillisPrecision( Date theDate) { + myIssued = new InstantDt(theDate); return this; } @@ -799,8 +824,8 @@ public class Observation extends BaseResource implements IResource { * *

*/ - public Observation setIssuedWithMillisPrecision( Date theDate) { - myIssued = new InstantDt(theDate); + public Observation setIssued( Date theDate, TemporalPrecisionEnum thePrecision) { + myIssued = new InstantDt(theDate, thePrecision); return this; } @@ -815,9 +840,9 @@ public class Observation extends BaseResource implements IResource { * The status of the result value *

*/ - public BoundCodeDt getStatus() { + public CodeDt getStatus() { if (myStatus == null) { - myStatus = new BoundCodeDt(ObservationStatusEnum.VALUESET_BINDER); + myStatus = new CodeDt(); } return myStatus; } @@ -830,25 +855,25 @@ public class Observation extends BaseResource implements IResource { * The status of the result value *

*/ - public Observation setStatus(BoundCodeDt theValue) { + public Observation setStatus(CodeDt theValue) { myStatus = theValue; return this; } - /** - * Sets the value(s) for status (registered | preliminary | final | amended +) + /** + * Sets the value for status (registered | preliminary | final | amended +) * *

* Definition: * The status of the result value *

*/ - public Observation setStatus(ObservationStatusEnum theValue) { - getStatus().setValueAsEnum(theValue); - return this; + public Observation setStatus( String theCode) { + myStatus = new CodeDt(theCode); + return this; } - + /** * Gets the value(s) for reliability (ok | ongoing | early | questionable | calibrating | error +). * creating it if it does @@ -859,9 +884,9 @@ public class Observation extends BaseResource implements IResource { * An estimate of the degree to which quality issues have impacted on the value reported *

*/ - public BoundCodeDt getReliability() { + public CodeDt getReliability() { if (myReliability == null) { - myReliability = new BoundCodeDt(ObservationReliabilityEnum.VALUESET_BINDER); + myReliability = new CodeDt(); } return myReliability; } @@ -874,25 +899,25 @@ public class Observation extends BaseResource implements IResource { * An estimate of the degree to which quality issues have impacted on the value reported *

*/ - public Observation setReliability(BoundCodeDt theValue) { + public Observation setReliability(CodeDt theValue) { myReliability = theValue; return this; } - /** - * Sets the value(s) for reliability (ok | ongoing | early | questionable | calibrating | error +) + /** + * Sets the value for reliability (ok | ongoing | early | questionable | calibrating | error +) * *

* Definition: * An estimate of the degree to which quality issues have impacted on the value reported *

*/ - public Observation setReliability(ObservationReliabilityEnum theValue) { - getReliability().setValueAsEnum(theValue); - return this; + public Observation setReliability( String theCode) { + myReliability = new CodeDt(theCode); + return this; } - + /** * Gets the value(s) for bodySite (Observed body part). * creating it if it does @@ -993,8 +1018,8 @@ public class Observation extends BaseResource implements IResource { * A unique identifier for the simple observation *

*/ - public Observation setIdentifier( IdentifierUseEnum theUse, String theSystem, String theValue, String theLabel) { - myIdentifier = new IdentifierDt(theUse, theSystem, theValue, theLabel); + public Observation setIdentifier( String theSystem, String theValue) { + myIdentifier = new IdentifierDt(theSystem, theValue); return this; } @@ -1006,8 +1031,8 @@ public class Observation extends BaseResource implements IResource { * A unique identifier for the simple observation *

*/ - public Observation setIdentifier( String theSystem, String theValue) { - myIdentifier = new IdentifierDt(theSystem, theValue); + public Observation setIdentifier( IdentifierUseEnum theUse, String theSystem, String theValue, String theLabel) { + myIdentifier = new IdentifierDt(theUse, theSystem, theValue, theLabel); return this; } @@ -1324,32 +1349,6 @@ public class Observation extends BaseResource implements IResource { *

* Definition: * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 - *

- */ - public ReferenceRange setLow( QuantityCompararatorEnum theComparator, double theValue, String theSystem, String theUnits) { - myLow = new QuantityDt(theComparator, theValue, theSystem, theUnits); - return this; - } - - /** - * Sets the value for low (Low Range, if relevant) - * - *

- * Definition: - * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 - *

- */ - public ReferenceRange setLow( QuantityCompararatorEnum theComparator, long theValue, String theSystem, String theUnits) { - myLow = new QuantityDt(theComparator, theValue, theSystem, theUnits); - return this; - } - - /** - * Sets the value for low (Low Range, if relevant) - * - *

- * Definition: - * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 *

*/ public ReferenceRange setLow( QuantityCompararatorEnum theComparator, double theValue, String theUnits) { @@ -1363,19 +1362,6 @@ public class Observation extends BaseResource implements IResource { *

* Definition: * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 - *

- */ - public ReferenceRange setLow( QuantityCompararatorEnum theComparator, long theValue, String theUnits) { - myLow = new QuantityDt(theComparator, theValue, theUnits); - return this; - } - - /** - * Sets the value for low (Low Range, if relevant) - * - *

- * Definition: - * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 *

*/ public ReferenceRange setLow( double theValue) { @@ -1396,6 +1382,45 @@ public class Observation extends BaseResource implements IResource { return this; } + /** + * Sets the value for low (Low Range, if relevant) + * + *

+ * Definition: + * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 + *

+ */ + public ReferenceRange setLow( QuantityCompararatorEnum theComparator, long theValue, String theUnits) { + myLow = new QuantityDt(theComparator, theValue, theUnits); + return this; + } + + /** + * Sets the value for low (Low Range, if relevant) + * + *

+ * Definition: + * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 + *

+ */ + public ReferenceRange setLow( QuantityCompararatorEnum theComparator, double theValue, String theSystem, String theUnits) { + myLow = new QuantityDt(theComparator, theValue, theSystem, theUnits); + return this; + } + + /** + * Sets the value for low (Low Range, if relevant) + * + *

+ * Definition: + * The value of the low bound of the reference range. If this is omitted, the low bound of the reference range is assumed to be meaningless. E.g. <2.3 + *

+ */ + public ReferenceRange setLow( QuantityCompararatorEnum theComparator, long theValue, String theSystem, String theUnits) { + myLow = new QuantityDt(theComparator, theValue, theSystem, theUnits); + return this; + } + /** * Gets the value(s) for high (High Range, if relevant). @@ -1433,32 +1458,6 @@ public class Observation extends BaseResource implements IResource { *

* Definition: * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 - *

- */ - public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, double theValue, String theSystem, String theUnits) { - myHigh = new QuantityDt(theComparator, theValue, theSystem, theUnits); - return this; - } - - /** - * Sets the value for high (High Range, if relevant) - * - *

- * Definition: - * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 - *

- */ - public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, long theValue, String theSystem, String theUnits) { - myHigh = new QuantityDt(theComparator, theValue, theSystem, theUnits); - return this; - } - - /** - * Sets the value for high (High Range, if relevant) - * - *

- * Definition: - * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 *

*/ public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, double theValue, String theUnits) { @@ -1472,19 +1471,6 @@ public class Observation extends BaseResource implements IResource { *

* Definition: * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 - *

- */ - public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, long theValue, String theUnits) { - myHigh = new QuantityDt(theComparator, theValue, theUnits); - return this; - } - - /** - * Sets the value for high (High Range, if relevant) - * - *

- * Definition: - * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 *

*/ public ReferenceRange setHigh( double theValue) { @@ -1505,6 +1491,45 @@ public class Observation extends BaseResource implements IResource { return this; } + /** + * Sets the value for high (High Range, if relevant) + * + *

+ * Definition: + * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 + *

+ */ + public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, long theValue, String theUnits) { + myHigh = new QuantityDt(theComparator, theValue, theUnits); + return this; + } + + /** + * Sets the value for high (High Range, if relevant) + * + *

+ * Definition: + * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 + *

+ */ + public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, double theValue, String theSystem, String theUnits) { + myHigh = new QuantityDt(theComparator, theValue, theSystem, theUnits); + return this; + } + + /** + * Sets the value for high (High Range, if relevant) + * + *

+ * Definition: + * The value of the high bound of the reference range. If this is omitted, the high bound of the reference range is assumed to be meaningless. E.g. >5 + *

+ */ + public ReferenceRange setHigh( QuantityCompararatorEnum theComparator, long theValue, String theSystem, String theUnits) { + myHigh = new QuantityDt(theComparator, theValue, theSystem, theUnits); + return this; + } + /** * Gets the value(s) for meaning (Indicates the meaning/use of this range of this range). @@ -1588,7 +1613,7 @@ public class Observation extends BaseResource implements IResource { shortDefinition="has-component | has-member | derived-from | sequel-to | replaces | qualified-by | interfered-by", formalDefinition="A code specifying the kind of relationship that exists with the target observation" ) - private BoundCodeDt myType; + private CodeDt myType; @Child(name="target", order=1, min=1, max=1, type={ ca.uhn.fhir.model.dstu.resource.Observation.class }) @@ -1619,9 +1644,9 @@ public class Observation extends BaseResource implements IResource { * A code specifying the kind of relationship that exists with the target observation *

*/ - public BoundCodeDt getType() { + public CodeDt getType() { if (myType == null) { - myType = new BoundCodeDt(ObservationRelationshipTypeEnum.VALUESET_BINDER); + myType = new CodeDt(); } return myType; } @@ -1634,25 +1659,25 @@ public class Observation extends BaseResource implements IResource { * A code specifying the kind of relationship that exists with the target observation *

*/ - public Related setType(BoundCodeDt theValue) { + public Related setType(CodeDt theValue) { myType = theValue; return this; } - /** - * Sets the value(s) for type (has-component | has-member | derived-from | sequel-to | replaces | qualified-by | interfered-by) + /** + * Sets the value for type (has-component | has-member | derived-from | sequel-to | replaces | qualified-by | interfered-by) * *

* Definition: * A code specifying the kind of relationship that exists with the target observation *

*/ - public Related setType(ObservationRelationshipTypeEnum theValue) { - getType().setValueAsEnum(theValue); - return this; + public Related setType( String theCode) { + myType = new CodeDt(theCode); + return this; } - + /** * Gets the value(s) for target (Observation that is related to this one). * creating it if it does @@ -1687,7 +1712,15 @@ public class Observation extends BaseResource implements IResource { } + public void setStatus(ObservationStatusEnum theFinal) { + if (theFinal==null) { + getStatus().setValue(null); + }else { + getStatus().setValue(theFinal.getCode()); + } + } -} + +} \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java index 3e1d42bdee7..57489041793 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IdDt.java @@ -35,11 +35,12 @@ import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.server.Constants; /** - * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, Resource References, etc. to represent a specific instance of a resource. + * Represents the FHIR ID type. This is the actual resource ID, meaning the ID that will be used in RESTful URLs, + * Resource References, etc. to represent a specific instance of a resource. * *

- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length - * limit of 36 characters. + * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any + * other combination of lowercase letters, numerals, "-" and ".", with a length limit of 36 characters. *

*

* regex: [a-z0-9\-\.]{1,36} @@ -52,7 +53,7 @@ public class IdDt extends BasePrimitive { private String myResourceType; private String myUnqualifiedId; private String myUnqualifiedVersionId; - private String myValue; + private volatile String myValue; /** * Create a new empty ID @@ -62,7 +63,8 @@ public class IdDt extends BasePrimitive { } /** - * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string representation. + * Create a new ID, using a BigDecimal input. Uses {@link BigDecimal#toPlainString()} to generate the string + * representation. */ public IdDt(BigDecimal thePid) { if (thePid != null) { @@ -80,11 +82,12 @@ public class IdDt extends BasePrimitive { } /** - * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete URL (http://example.com/fhir/Patient/1234). + * Create a new ID using a string. This String may contain a simple ID (e.g. "1234") or it may contain a complete + * URL (http://example.com/fhir/Patient/1234). * *

- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length - * limit of 36 characters. + * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or + * any other combination of lowercase letters, numerals, "-" and ".", with a length limit of 36 characters. *

*

* regex: [a-z0-9\-\.]{1,36} @@ -107,13 +110,6 @@ public class IdDt extends BasePrimitive { this(theResourceType, toPlainStringWithNpeThrowIfNeeded(theIdPart)); } - private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) { - if (theIdPart==null) { - throw new NullPointerException("BigDecimal ID can not be null"); - } - return theIdPart.toPlainString(); - } - /** * Constructor * @@ -144,7 +140,8 @@ public class IdDt extends BasePrimitive { } /** - * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is ambiguous) + * @deprecated Use {@link #getIdPartAsBigDecimal()} instead (this method was deprocated because its name is + * ambiguous) */ public BigDecimal asBigDecimal() { return getIdPartAsBigDecimal(); @@ -165,7 +162,8 @@ public class IdDt extends BasePrimitive { } /** - * Returns a reference to this IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API. + * Returns a reference to this IdDt. It is generally not neccesary to use this method but it is + * provided for consistency with the rest of the API. */ @Override public IdDt getId() { @@ -209,18 +207,27 @@ public class IdDt extends BasePrimitive { } /** - * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a simple ID. Use {@link #getIdPart()} to get just the ID portion. + * Returns the value of this ID. Note that this value may be a fully qualified URL, a relative/partial URL, or a + * simple ID. Use {@link #getIdPart()} to get just the ID portion. * * @see #getIdPart() */ @Override public String getValue() { if (myValue == null && myHaveComponentParts) { - if (myUnqualifiedVersionId != null) { - myValue = myResourceType + '/' + myUnqualifiedId + '/' + Constants.PARAM_HISTORY + '/' + myUnqualifiedVersionId; - } else { - myValue = myResourceType + '/' + myUnqualifiedId; + StringBuilder b = new StringBuilder(); + if (isNotBlank(myResourceType)) { + b.append(myResourceType); + b.append('/'); } + b.append(myUnqualifiedId); + if (isNotBlank(myUnqualifiedVersionId)) { + b.append('/'); + b.append(Constants.PARAM_HISTORY); + b.append('/'); + b.append(myUnqualifiedVersionId); + } + myValue = b.toString(); } return myValue; } @@ -255,7 +262,8 @@ public class IdDt extends BasePrimitive { } /** - * Returns true if the unqualified ID is a valid {@link Long} value (in other words, it consists only of digits) + * Returns true if the unqualified ID is a valid {@link Long} value (in other words, it consists only + * of digits) */ public boolean isIdPartValidLong() { String id = getIdPart(); @@ -278,7 +286,8 @@ public class IdDt extends BasePrimitive { } /** - * Copies the value from the given IdDt to this IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API. + * Copies the value from the given IdDt to this IdDt. It is generally not neccesary to use this method + * but it is provided for consistency with the rest of the API. */ @Override public void setId(IdDt theId) { @@ -289,8 +298,8 @@ public class IdDt extends BasePrimitive { * Set the value * *

- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length - * limit of 36 characters. + * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or + * any other combination of lowercase letters, numerals, "-" and ".", with a length limit of 36 characters. *

*

* regex: [a-z0-9\-\.]{1,36} @@ -300,7 +309,7 @@ public class IdDt extends BasePrimitive { public void setValue(String theValue) throws DataFormatException { // TODO: add validation myValue = theValue; - myHaveComponentParts=false; + myHaveComponentParts = false; if (StringUtils.isBlank(theValue)) { myValue = null; myUnqualifiedId = null; @@ -337,8 +346,8 @@ public class IdDt extends BasePrimitive { * Set the value * *

- * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or any other combination of lowercase letters, numerals, "-" and ".", with a length - * limit of 36 characters. + * Description: A whole number in the range 0 to 2^64-1 (optionally represented in hex), a uuid, an oid, or + * any other combination of lowercase letters, numerals, "-" and ".", with a length limit of 36 characters. *

*

* regex: [a-z0-9\-\.]{1,36} @@ -372,9 +381,15 @@ public class IdDt extends BasePrimitive { } } + public IdDt withResourceType(String theResourceName) { + return new IdDt(theResourceName, getIdPart(), getVersionIdPart()); + } + /** - * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be used if the ID does not already contain those respective parts). Essentially, - * because IdDt can contain either a complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete URL. + * Returns a view of this ID as a fully qualified URL, given a server base and resource name (which will only be + * used if the ID does not already contain those respective parts). Essentially, because IdDt can contain either a + * complete URL or a partial one (or even jut a simple ID), this method may be used to translate into a complete + * URL. * * @param theServerBase * The server base (e.g. "http://example.com/fhir") @@ -398,29 +413,31 @@ public class IdDt extends BasePrimitive { } retVal.append('/'); retVal.append(getIdPart()); - + if (hasVersionIdPart()) { retVal.append('/'); retVal.append(Constants.PARAM_HISTORY); retVal.append('/'); - retVal.append(getVersionIdPart()); + retVal.append(getVersionIdPart()); } - + return retVal.toString(); } /** - * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID noted by theVersion. + * Creates a new instance of this ID which is identical, but refers to the specific version of this resource ID + * noted by theVersion. * * @param theVersion * The actual version string, e.g. "1" - * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted by theVersion. + * @return A new instance of IdDt which is identical, but refers to the specific version of this resource ID noted + * by theVersion. */ public IdDt withVersion(String theVersion) { Validate.notBlank(theVersion, "Version may not be null or empty"); String existingValue = getValue(); - + int i = existingValue.indexOf(Constants.PARAM_HISTORY); String value; if (i > 1) { @@ -432,8 +449,11 @@ public class IdDt extends BasePrimitive { return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion); } - public IdDt withResourceType(String theResourceName) { - return new IdDt(theResourceName, getIdPart(), getVersionIdPart()); + private static String toPlainStringWithNpeThrowIfNeeded(BigDecimal theIdPart) { + if (theIdPart == null) { + throw new NullPointerException("BigDecimal ID can not be null"); + } + return theIdPart.toPlainString(); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java index 71d45aa55cd..6da5fd9818f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java @@ -59,6 +59,18 @@ public class InstantDt extends BaseDateTimeDt { setTimeZone(theCalendar.getTimeZone()); } + /** + * Create a new DateTimeDt using an existing value. Use this constructor with caution, + * as it may create more precision than warranted (since for example it is possible to pass in + * a DateTime with only a year, and this constructor will convert to an InstantDt with + * milliseconds precision). + */ + public InstantDt(BaseDateTimeDt theDateTime) { + setValue(theDateTime.getValue()); + setPrecision(DEFAULT_PRECISION); + setTimeZone(theDateTime.getTimeZone()); + } + /** * Create a new DateTimeDt */ diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java index 0ff2b285b11..2d76eb586c7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OptionalParam.java @@ -23,18 +23,55 @@ package ca.uhn.fhir.rest.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.rest.param.ReferenceParam; @Retention(RetentionPolicy.RUNTIME) +/** + * Parameter annotation which specifies a search parameter for a {@link Search} method. + */ public @interface OptionalParam { - String name(); + /** + * This is the name for the parameter. Generally this should be a + * simple string (e.g. "name", or "identifier") which will be the name + * of the URL parameter used to populate this method parameter. + *

+ * Most resource model classes have constants which may be used to + * supply values for this field, e.g. {@link Patient#SP_NAME} or + * {@link Observation#SP_DATE} + *

+ *

+ * If you wish to specify a parameter for a resource reference which + * only accepts a specific chained value, it is also valid to supply + * a chained name here, such as "patient.name". It is recommended to + * supply this using constants where possible, e.g. + * {@link Observation#SP_SUBJECT} + '.' + {@link Patient#SP_IDENTIFIER} + *

+ */ + String name(); /** * For resource reference parameters ({@link ReferenceParam}) this parameter may be - * used to indicate the resource type(s) which may be referenced by this param + * used to indicate the resource type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

*/ Class[] targetTypes() default {}; + /** + * For composite parameters ({@link CompositeParam}) this parameter may be + * used to indicate the parameter type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link CompositeParam}, + * this value must not be populated. + *

+ */ + Class[] compositeTypes() default {}; + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java index cfc4fa5679f..9385c605335 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/RequiredParam.java @@ -23,18 +23,56 @@ package ca.uhn.fhir.rest.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.rest.param.ReferenceParam; @Retention(RetentionPolicy.RUNTIME) - +/** + * Parameter annotation which specifies a search parameter for a {@link Search} method. + */ public @interface RequiredParam { - String name(); + /** + * This is the name for the parameter. Generally this should be a + * simple string (e.g. "name", or "identifier") which will be the name + * of the URL parameter used to populate this method parameter. + *

+ * Most resource model classes have constants which may be used to + * supply values for this field, e.g. {@link Patient#SP_NAME} or + * {@link Observation#SP_DATE} + *

+ *

+ * If you wish to specify a parameter for a resource reference which + * only accepts a specific chained value, it is also valid to supply + * a chained name here, such as "patient.name". It is recommended to + * supply this using constants where possible, e.g. + * {@link Observation#SP_SUBJECT} + '.' + {@link Patient#SP_IDENTIFIER} + *

+ */ + String name(); /** * For resource reference parameters ({@link ReferenceParam}) this parameter may be - * used to indicate the resource type(s) which may be referenced by this param + * used to indicate the resource type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link ReferenceParam}, + * this value must not be populated. + *

*/ Class[] targetTypes() default {}; + + + /** + * For composite parameters ({@link CompositeParam}) this parameter may be + * used to indicate the parameter type(s) which may be referenced by this param. + *

+ * If the parameter annotated with this annotation is not a {@link CompositeParam}, + * this value must not be populated. + *

+ */ + Class[] compositeTypes() default {}; + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index d193b2cbc75..829319d5797 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -67,6 +67,8 @@ import ca.uhn.fhir.rest.gclient.ISort; import ca.uhn.fhir.rest.gclient.ITransaction; import ca.uhn.fhir.rest.gclient.ITransactionTyped; import ca.uhn.fhir.rest.gclient.IUntypedQuery; +import ca.uhn.fhir.rest.gclient.IUpdate; +import ca.uhn.fhir.rest.gclient.IUpdateTyped; import ca.uhn.fhir.rest.method.DeleteMethodBinding; import ca.uhn.fhir.rest.method.HistoryMethodBinding; import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation; @@ -77,7 +79,6 @@ import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.ReadMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.method.TransactionMethodBinding; -import ca.uhn.fhir.rest.method.UpdateMethodBinding; import ca.uhn.fhir.rest.method.ValidateMethodBinding; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; @@ -278,7 +279,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public MethodOutcome update(IdDt theIdDt, IResource theResource) { - BaseHttpClientInvocation invocation = UpdateMethodBinding.createUpdateInvocation(theResource, theIdDt, null, myContext); + BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(theResource, null, theIdDt, myContext); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); } @@ -344,6 +345,24 @@ public class GenericClient extends BaseClient implements IGenericClient { return (T) this; } + protected IResource parseResourceBody(String theResourceBody) { + EncodingEnum encoding = null; + for (int i = 0; i < theResourceBody.length() && encoding == null; i++) { + switch (theResourceBody.charAt(i)) { + case '<': + encoding = EncodingEnum.XML; + break; + case '{': + encoding = EncodingEnum.JSON; + break; + } + } + if (encoding == null) { + throw new InvalidRequestException("FHIR client can't determine resource encoding"); + } + return encoding.newParser(myContext).parseResource(theResourceBody); + } + @SuppressWarnings("unchecked") @Override public T encodedJson() { @@ -419,23 +438,10 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public MethodOutcome execute() { if (myResource == null) { - EncodingEnum encoding = null; - for (int i = 0; i < myResourceBody.length() && encoding == null; i++) { - switch (myResourceBody.charAt(i)) { - case '<': - encoding = EncodingEnum.XML; - break; - case '{': - encoding = EncodingEnum.JSON; - break; - } - } - if (encoding == null) { - throw new InvalidRequestException("FHIR client can't determine resource encoding"); - } - myResource = encoding.newParser(myContext).parseResource(myResourceBody); + myResource = parseResourceBody(myResourceBody); } - + myId = getPreferredId(myResource, myId); + BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); @@ -448,6 +454,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } + @Override public ICreateTyped resource(IResource theResource) { Validate.notNull(theResource, "Resource can not be null"); @@ -475,6 +482,76 @@ public class GenericClient extends BaseClient implements IGenericClient { } } + + private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped { + + private IdDt myId; + private String myResourceBody; + private IResource myResource; + + @Override + public MethodOutcome execute() { + if (myResource == null) { + myResource = parseResourceBody(myResourceBody); + } + if (myId == null) { + myId = myResource.getId(); + } + if (myId==null || myId.hasIdPart() == false) { + throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); + } + + BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); + + RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); + final String resourceName = def.getName(); + + OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); + + Map> params = new HashMap>(); + return invoke(params, binding, invocation); + + } + + @Override + public IUpdateTyped resource(IResource theResource) { + Validate.notNull(theResource, "Resource can not be null"); + myResource = theResource; + return this; + } + + @Override + public IUpdateTyped resource(String theResourceBody) { + Validate.notBlank(theResourceBody, "Body can not be null or blank"); + myResourceBody = theResourceBody; + return this; + } + + @Override + public IUpdateTyped withId(IdDt theId) { + if (theId == null) { + throw new NullPointerException("theId can not be null"); + } + if (theId.hasIdPart()==false) { + throw new NullPointerException("theId must not be blank and must contain an ID, found: "+theId.getValue()); + } + myId = theId; + return this; + } + + @Override + public IUpdateTyped withId(String theId) { + if (theId == null) { + throw new NullPointerException("theId can not be null"); + } + if (isBlank(theId)) { + throw new NullPointerException("theId must not be blank and must contain an ID, found: "+theId); + } + myId=new IdDt(theId); + return this; + } + + } private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped { @@ -924,4 +1001,17 @@ public class GenericClient extends BaseClient implements IGenericClient { } + protected String getPreferredId(IResource theResource, String theId) { + if (isNotBlank(theId)) { + return theId; + } + return theResource.getId().getIdPart(); + } + + @Override + public IUpdate update() { + return new UpdateInternal(); + } + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java index 96471e63e98..2f9a7971548 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java @@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.gclient.IGetPage; import ca.uhn.fhir.rest.gclient.IGetTags; import ca.uhn.fhir.rest.gclient.ITransaction; import ca.uhn.fhir.rest.gclient.IUntypedQuery; +import ca.uhn.fhir.rest.gclient.IUpdate; public interface IGenericClient { /** @@ -268,4 +269,9 @@ public interface IGenericClient { */ IDelete delete(); + /** + * Fluent method for the "update" operation, which performs a logical delete on a server resource + */ + IUpdate update(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeClientParam.java index 90cf669709f..7e2c84450de 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeClientParam.java @@ -21,23 +21,25 @@ package ca.uhn.fhir.rest.gclient; */ /** - * Composite params are not yet supported - This is a placeholder. Help welcome! - * - * TODO: implement + * Composite parameter type for use in fluent client interfaces */ -public class CompositeClientParam { +public class CompositeClientParam implements IParam { + + private String myName; public CompositeClientParam(String theName) { - // TODO Auto-generated constructor stub + myName=theName; } - /** - * Composite params are not yet supported - This is a placeholder. Help welcome! - * - * TODO: implement - */ - public void notYetSupported() { - //nothig + + @Override + public String getParamName() { + return myName; } + public ICompositeWithLeft withLeft(ICriterion theLeft) { + return new CompositeCriterion(myName, theLeft); + } + + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeCriterion.java new file mode 100644 index 00000000000..3f2970ca915 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeCriterion.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.rest.gclient; + +import static org.apache.commons.lang3.StringUtils.*; + +public class CompositeCriterion implements ICompositeWithLeft, ICriterion, ICriterionInternal { + + private ICriterion myRight; + private String myName; + private ICriterion myLeft; + + public CompositeCriterion(String theName, ICriterion theLeft) { + myName = theName; + myLeft = theLeft; + } + + @Override + public ICriterion withRight(ICriterion theRight) { + myRight = theRight; + return this; + } + + @Override + public String getParameterValue() { + ICriterionInternal left = (ICriterionInternal) myLeft; + ICriterionInternal right = (ICriterionInternal) myRight; + return defaultString(left.getParameterValue()) + '$' + defaultString(right.getParameterValue()); + } + + @Override + public String getParameterName() { + return myName; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeParam.java index 356a8c44d10..839e9972227 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/CompositeParam.java @@ -22,10 +22,10 @@ package ca.uhn.fhir.rest.gclient; /** - * @deprecated Use {@link CompositeClientParam} instead. That class is identical to this one but was renamed to reduct + * @deprecated Use {@link CompositeClientParam} instead. That class is identical to this one but was renamed to reduce * confusing duplicate names in the API. This class will be removed in a future release. */ -public class CompositeParam extends CompositeClientParam { +public class CompositeParam extends CompositeClientParam { public CompositeParam(String theParamName) { super(theParamName); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java index 45fb524940c..21d9d0a386c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateClientParam.java @@ -61,7 +61,7 @@ public class DateClientParam implements IParam { return new DateWithPrefix(""); } - private class Criterion implements ICriterion, ICriterionInternal { + private class Criterion implements ICriterion, ICriterionInternal { private String myValue; @@ -89,35 +89,35 @@ public class DateClientParam implements IParam { } @Override - public ICriterion day(Date theValue) { + public ICriterion day(Date theValue) { DateTimeDt dt = new DateTimeDt(theValue); dt.setPrecision(TemporalPrecisionEnum.DAY); return new Criterion(myPrefix + dt.getValueAsString()); } @Override - public ICriterion day(String theValue) { + public ICriterion day(String theValue) { DateTimeDt dt = new DateTimeDt(theValue); dt.setPrecision(TemporalPrecisionEnum.DAY); return new Criterion(myPrefix + dt.getValueAsString()); } @Override - public ICriterion now() { + public ICriterion now() { DateTimeDt dt = new DateTimeDt(); dt.setPrecision(TemporalPrecisionEnum.DAY); return new Criterion(myPrefix + dt.getValueAsString()); } @Override - public ICriterion second(Date theValue) { + public ICriterion second(Date theValue) { DateTimeDt dt = new DateTimeDt(theValue); dt.setPrecision(TemporalPrecisionEnum.DAY); return new Criterion(myPrefix + dt.getValueAsString()); } @Override - public ICriterion second(String theValue) { + public ICriterion second(String theValue) { DateTimeDt dt = new DateTimeDt(theValue); dt.setPrecision(TemporalPrecisionEnum.DAY); return new Criterion(myPrefix + dt.getValueAsString()); @@ -127,15 +127,15 @@ public class DateClientParam implements IParam { public interface IDateSpecifier { - ICriterion day(Date theValue); + ICriterion day(Date theValue); - ICriterion day(String theValue); + ICriterion day(String theValue); - ICriterion now(); + ICriterion now(); - ICriterion second(Date theValue); + ICriterion second(Date theValue); - ICriterion second(String theValue); + ICriterion second(String theValue); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICompositeWithLeft.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICompositeWithLeft.java new file mode 100644 index 00000000000..fb5113041be --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICompositeWithLeft.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.rest.gclient; + +public interface ICompositeWithLeft { + + ICriterion withRight(ICriterion theRight); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java index 6505199c951..e99a100776d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java @@ -20,6 +20,6 @@ package ca.uhn.fhir.rest.gclient; * #L% */ -public interface ICriterion { +public interface ICriterion { } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index 41c5abaaab1..db5847f7cb1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -25,9 +25,9 @@ import ca.uhn.fhir.model.api.Include; public interface IQuery extends IClientExecutable { - IQuery where(ICriterion theCriterion); + IQuery where(ICriterion theCriterion); - IQuery and(ICriterion theCriterion); + IQuery and(ICriterion theCriterion); IQuery include(Include theIncludeManagingorganization); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdate.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdate.java new file mode 100644 index 00000000000..4b0c7fc40c1 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdate.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.rest.gclient; + +import ca.uhn.fhir.model.api.IResource; + +public interface IUpdate { + + IUpdateTyped resource(IResource theResource); + + IUpdateTyped resource(String theResourceBody); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java new file mode 100644 index 00000000000..3b23b2de1f3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java @@ -0,0 +1,11 @@ +package ca.uhn.fhir.rest.gclient; + +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.MethodOutcome; + +public interface IUpdateTyped extends IClientExecutable { + + IUpdateTyped withId(IdDt theId); + + IUpdateTyped withId(String theId); +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/NumberClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/NumberClientParam.java index f993930b053..5d06ca9bc13 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/NumberClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/NumberClientParam.java @@ -31,16 +31,16 @@ public class NumberClientParam implements IParam { myParamName = theParamName; } - public IMatches exactly() { - return new IMatches() { + public IMatches> exactly() { + return new IMatches>() { @Override - public ICriterion number(long theNumber) { - return new StringCriterion(getParamName(), Long.toString(theNumber)); + public ICriterion number(long theNumber) { + return new StringCriterion(getParamName(), Long.toString(theNumber)); } @Override - public ICriterion number(String theNumber) { - return new StringCriterion(getParamName(), (theNumber)); + public ICriterion number(String theNumber) { + return new StringCriterion(getParamName(), (theNumber)); } }; } @@ -50,58 +50,58 @@ public class NumberClientParam implements IParam { return myParamName; } - public IMatches greaterThan() { - return new IMatches() { + public IMatches> greaterThan() { + return new IMatches>() { @Override - public ICriterion number(long theNumber) { - return new StringCriterion(getParamName(), ">" + Long.toString(theNumber)); + public ICriterion number(long theNumber) { + return new StringCriterion(getParamName(), ">" + Long.toString(theNumber)); } @Override - public ICriterion number(String theNumber) { - return new StringCriterion(getParamName(), ">" + (theNumber)); + public ICriterion number(String theNumber) { + return new StringCriterion(getParamName(), ">" + (theNumber)); } }; } - public IMatches greaterThanOrEqual() { - return new IMatches() { + public IMatches> greaterThanOrEqual() { + return new IMatches>() { @Override - public ICriterion number(long theNumber) { - return new StringCriterion(getParamName(), ">=" + Long.toString(theNumber)); + public ICriterion number(long theNumber) { + return new StringCriterion(getParamName(), ">=" + Long.toString(theNumber)); } @Override - public ICriterion number(String theNumber) { - return new StringCriterion(getParamName(), ">=" + (theNumber)); + public ICriterion number(String theNumber) { + return new StringCriterion(getParamName(), ">=" + (theNumber)); } }; } - public IMatches lessThan() { - return new IMatches() { + public IMatches> lessThan() { + return new IMatches>() { @Override - public ICriterion number(long theNumber) { - return new StringCriterion(getParamName(), "<" + Long.toString(theNumber)); + public ICriterion number(long theNumber) { + return new StringCriterion(getParamName(), "<" + Long.toString(theNumber)); } @Override - public ICriterion number(String theNumber) { - return new StringCriterion(getParamName(), "<" + (theNumber)); + public ICriterion number(String theNumber) { + return new StringCriterion(getParamName(), "<" + (theNumber)); } }; } - public IMatches lessThanOrEqual() { - return new IMatches() { + public IMatches> lessThanOrEqual() { + return new IMatches>() { @Override - public ICriterion number(long theNumber) { - return new StringCriterion(getParamName(), "<=" + Long.toString(theNumber)); + public ICriterion number(long theNumber) { + return new StringCriterion(getParamName(), "<=" + Long.toString(theNumber)); } @Override - public ICriterion number(String theNumber) { - return new StringCriterion(getParamName(), "<=" + (theNumber)); + public ICriterion number(String theNumber) { + return new StringCriterion(getParamName(), "<=" + (theNumber)); } }; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityClientParam.java index 9045801b4b3..d3a65b70359 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityClientParam.java @@ -141,11 +141,11 @@ public class QuantityClientParam implements IParam { public interface IAndUnits { - ICriterion andNoUnits(); + ICriterion andNoUnits(); - ICriterion andUnits(String theUnits); + ICriterion andUnits(String theUnits); - ICriterion andUnits(String theSystem, String theUnits); + ICriterion andUnits(String theSystem, String theUnits); } private class AndUnits implements IAndUnits { @@ -157,18 +157,18 @@ public class QuantityClientParam implements IParam { } @Override - public ICriterion andNoUnits() { + public ICriterion andNoUnits() { return andUnits(null, null); } @Override - public ICriterion andUnits(String theUnits) { + public ICriterion andUnits(String theUnits) { return andUnits(theUnits, null); } @Override - public ICriterion andUnits(String theSystem, String theUnits) { - return new StringCriterion(getParamName(), myToken1 + "|" + defaultString(theSystem) + "|" + defaultString(theUnits)); + public ICriterion andUnits(String theSystem, String theUnits) { + return new QuantityCriterion(getParamName(), myToken1 , defaultString(theSystem) , defaultString(theUnits)); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityCriterion.java new file mode 100644 index 00000000000..91b14411c04 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/QuantityCriterion.java @@ -0,0 +1,49 @@ +package ca.uhn.fhir.rest.gclient; + +import ca.uhn.fhir.rest.param.ParameterUtil; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 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% + */ + +class QuantityCriterion implements ICriterion, ICriterionInternal { + + private String myValue; + private String myName; + private String mySystem; + private String myUnits; + + public QuantityCriterion(String theParamName, String theValue, String theSystem, String theUnits) { + myValue = theValue; + myName = theParamName; + mySystem = theSystem; + myUnits = theUnits; + } + + @Override + public String getParameterName() { + return myName; + } + + @Override + public String getParameterValue() { + return ParameterUtil.escape(myValue) + '|' + ParameterUtil.escape(mySystem) + '|' + ParameterUtil.escape(myUnits); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceClientParam.java index 0777a59f466..f2735ffef4e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceClientParam.java @@ -36,32 +36,32 @@ public class ReferenceClientParam implements IParam { return myName; } - public ICriterion hasChainedProperty(ICriterion theICriterion) { - return new ReferenceChainCriterion(getParamName(), theICriterion); + public ICriterion hasChainedProperty(ICriterion theCriterion) { + return new ReferenceChainCriterion(getParamName(), theCriterion); } /** * Match the referenced resource if the resource has the given ID (this can be * the logical ID or the absolute URL of the resource) */ - public ICriterion hasId(IdDt theId) { - return new StringCriterion(getParamName(), theId.getValue()); + public ICriterion hasId(IdDt theId) { + return new StringCriterion(getParamName(), theId.getValue()); } /** * Match the referenced resource if the resource has the given ID (this can be * the logical ID or the absolute URL of the resource) */ - public ICriterion hasId(String theId) { - return new StringCriterion(getParamName(), theId); + public ICriterion hasId(String theId) { + return new StringCriterion(getParamName(), theId); } - private static class ReferenceChainCriterion implements ICriterion, ICriterionInternal { + private static class ReferenceChainCriterion implements ICriterion, ICriterionInternal { private String myParamName; private ICriterionInternal myWrappedCriterion; - public ReferenceChainCriterion(String theParamName, ICriterion theWrappedCriterion) { + public ReferenceChainCriterion(String theParamName, ICriterion theWrappedCriterion) { myParamName = theParamName; myWrappedCriterion = (ICriterionInternal) theWrappedCriterion; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java index 2ad23bfb7ec..e7839663898 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java @@ -59,33 +59,33 @@ public class StringClientParam implements IParam { public interface IStringMatch { - ICriterion value(String theValue); + ICriterion value(String theValue); - ICriterion value(StringDt theValue); + ICriterion value(StringDt theValue); } private class StringExactly implements IStringMatch { @Override - public ICriterion value(String theValue) { - return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue); + public ICriterion value(String theValue) { + return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue); } @Override - public ICriterion value(StringDt theValue) { - return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue.getValue()); + public ICriterion value(StringDt theValue) { + return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue.getValue()); } } private class StringMatches implements IStringMatch { @Override - public ICriterion value(String theValue) { - return new StringCriterion(getParamName(), theValue); + public ICriterion value(String theValue) { + return new StringCriterion(getParamName(), theValue); } @Override - public ICriterion value(StringDt theValue) { - return new StringCriterion(getParamName(), theValue.getValue()); + public ICriterion value(StringDt theValue) { + return new StringCriterion(getParamName(), theValue.getValue()); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java index 012d713e9f0..364f21e6099 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.gclient; +import ca.uhn.fhir.rest.param.ParameterUtil; + /* * #%L * HAPI FHIR - Core Library @@ -20,7 +22,7 @@ package ca.uhn.fhir.rest.gclient; * #L% */ -class StringCriterion implements ICriterion, ICriterionInternal { +class StringCriterion implements ICriterion, ICriterionInternal { private String myValue; private String myName; @@ -37,7 +39,7 @@ class StringCriterion implements ICriterion, ICriterionInternal { @Override public String getParameterValue() { - return myValue; + return ParameterUtil.escape(myValue); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java index 3e59ec11f57..9f368082b7a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.gclient; +import static org.apache.commons.lang3.StringUtils.*; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; /* @@ -42,27 +43,27 @@ public class TokenClientParam implements IParam { public IMatches exactly() { return new IMatches() { @Override - public ICriterion systemAndCode(String theSystem, String theCode) { - return new TokenCriterion(getParamName(), theSystem, theCode); + public ICriterion systemAndCode(String theSystem, String theCode) { + return new TokenCriterion(getParamName(), defaultString(theSystem), theCode); } @Override - public ICriterion systemAndIdentifier(String theSystem, String theCode) { - return new TokenCriterion(getParamName(), theSystem, theCode); + public ICriterion systemAndIdentifier(String theSystem, String theCode) { + return new TokenCriterion(getParamName(), defaultString(theSystem), theCode); } @Override - public ICriterion code(String theCode) { + public ICriterion code(String theCode) { return new TokenCriterion(getParamName(), null, theCode); } @Override - public ICriterion identifier(String theIdentifier) { + public ICriterion identifier(String theIdentifier) { return new TokenCriterion(getParamName(), null, theIdentifier); } @Override - public ICriterion identifier(IdentifierDt theIdentifier) { + public ICriterion identifier(IdentifierDt theIdentifier) { return new TokenCriterion(getParamName(), theIdentifier.getSystem().getValueAsString(), theIdentifier.getValue().getValue()); } }; @@ -78,7 +79,7 @@ public class TokenClientParam implements IParam { * The code * @return A criterion */ - ICriterion systemAndCode(String theSystem, String theCode); + ICriterion systemAndCode(String theSystem, String theCode); /** * Creates a search criterion that matches against the given system and identifier @@ -89,7 +90,7 @@ public class TokenClientParam implements IParam { * The identifier * @return A criterion */ - ICriterion systemAndIdentifier(String theSystem, String theIdentifier); + ICriterion systemAndIdentifier(String theSystem, String theIdentifier); /** * Creates a search criterion that matches against the given identifier, with no system specified @@ -98,7 +99,7 @@ public class TokenClientParam implements IParam { * The identifier * @return A criterion */ - ICriterion identifier(String theIdentifier); + ICriterion identifier(String theIdentifier); /** * Creates a search criterion that matches against the given code, with no code system specified @@ -107,7 +108,7 @@ public class TokenClientParam implements IParam { * The identifier * @return A criterion */ - ICriterion code(String theIdentifier); + ICriterion code(String theIdentifier); /** * Creates a search criterion that matches against the given identifier (system and code if both are present, or whatever is present) @@ -116,7 +117,7 @@ public class TokenClientParam implements IParam { * The identifier * @return A criterion */ - ICriterion identifier(IdentifierDt theIdentifier); + ICriterion identifier(IdentifierDt theIdentifier); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java index 836bbd8c218..973cd517174 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java @@ -22,17 +22,23 @@ package ca.uhn.fhir.rest.gclient; import org.apache.commons.lang3.StringUtils; -class TokenCriterion implements ICriterion, ICriterionInternal { +import ca.uhn.fhir.rest.param.ParameterUtil; + +class TokenCriterion implements ICriterion, ICriterionInternal { private String myValue; private String myName; public TokenCriterion(String theName, String theSystem, String theCode) { myName = theName; - if (StringUtils.isNotBlank(theSystem)) { - myValue = theSystem + "|" + StringUtils.defaultString(theCode); + String system = ParameterUtil.escape(theSystem); + String code = ParameterUtil.escape(theCode); + if (StringUtils.isNotBlank(system)) { + myValue = system + "|" + StringUtils.defaultString(code); + } else if (system == null) { + myValue = StringUtils.defaultString(code); } else { - myValue = "|" + StringUtils.defaultString(theCode); + myValue = "|" + StringUtils.defaultString(code); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseBinder.java new file mode 100644 index 00000000000..e04ec40157d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseBinder.java @@ -0,0 +1,60 @@ +package ca.uhn.fhir.rest.method; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.param.CompositeParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; + +class BaseBinder { + private Class[] myCompositeTypes; + private Constructor myConstructor; + private final Class myType; + + public BaseBinder(Class theType, Class[] theCompositeTypes) { + myType = theType; + myCompositeTypes = theCompositeTypes; + + if (myType.equals(CompositeParam.class)) { + if (myCompositeTypes.length != 2) { + throw new ConfigurationException("Search parameter of type " + myType.getName() + " must have 2 composite types declared in parameter annotation, found " + theCompositeTypes.length); + } + } + + try { + Class[] types = new Class[myCompositeTypes.length]; + for (int i = 0; i < myCompositeTypes.length; i++) { + types[i] = myCompositeTypes[i].getClass(); + } + myConstructor = myType.getConstructor(types); + } catch (NoSuchMethodException e) { + throw new ConfigurationException("Query parameter type " + theType.getName() + " has no constructor with types " + Arrays.asList(theCompositeTypes)); + } + } + + public T newInstance() { + try { + final Object[] args = new Object[myCompositeTypes.length]; + for (int i = 0; i < myCompositeTypes.length;i++) { + args[i] = myCompositeTypes[i];//.newInstance(); + } + + T dt = myConstructor.newInstance(args); + return dt; + } catch (final InstantiationException e) { + throw new InternalErrorException(e); + } catch (final IllegalAccessException e) { + throw new InternalErrorException(e); + } catch (final SecurityException e) { + throw new InternalErrorException(e); + } catch (final IllegalArgumentException e) { + throw new InternalErrorException(e); + } catch (final InvocationTargetException e) { + throw new InternalErrorException(e); + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 5fab315aff4..079b6d05e2d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -79,13 +79,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding> theHeaders, IResource resource) { List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { @@ -298,6 +330,7 @@ public class MethodUtil { parameter.setName(((RequiredParam) nextAnnotation).name()); parameter.setRequired(true); parameter.setDeclaredTypes(((RequiredParam) nextAnnotation).targetTypes()); + parameter.setCompositeTypes(((RequiredParam) nextAnnotation).compositeTypes()); parameter.setType(parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; @@ -306,6 +339,7 @@ public class MethodUtil { parameter.setName(((OptionalParam) nextAnnotation).name()); parameter.setRequired(false); parameter.setDeclaredTypes(((OptionalParam) nextAnnotation).targetTypes()); + parameter.setCompositeTypes(((OptionalParam) nextAnnotation).compositeTypes()); parameter.setType(parameterType, innerCollectionType, outerCollectionType); MethodUtil.extractDescription(parameter, annotations); param = parameter; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java index bcb03c05bce..6dde522b265 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java @@ -25,14 +25,14 @@ import java.util.List; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -final class QueryParameterAndBinder implements IParamBinder { - private final Class> myType; +final class QueryParameterAndBinder extends BaseBinder> implements IParamBinder { - QueryParameterAndBinder(Class> theType) { - myType = theType; + QueryParameterAndBinder(Class> theType, Class[] theCompositeTypes) { + super(theType, theCompositeTypes); } @SuppressWarnings("unchecked") @@ -46,12 +46,8 @@ final class QueryParameterAndBinder implements IParamBinder { public Object parse(List theString) throws InternalErrorException, InvalidRequestException { IQueryParameterAnd dt; try { - dt = myType.newInstance(); + dt = newInstance(); dt.setValuesAsQueryTokens(theString); - } catch (InstantiationException e) { - throw new InternalErrorException(e); - } catch (IllegalAccessException e) { - throw new InternalErrorException(e); } catch (SecurityException e) { throw new InternalErrorException(e); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java index d5b5efac68d..b0cb334395a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java @@ -25,14 +25,14 @@ import java.util.List; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -final class QueryParameterOrBinder implements IParamBinder { - private final Class> myType; +final class QueryParameterOrBinder extends BaseBinder> implements IParamBinder { - QueryParameterOrBinder(Class> theType) { - myType = theType; + QueryParameterOrBinder(Class> theType, Class[] theCompositeTypes) { + super(theType, theCompositeTypes); } @SuppressWarnings("unchecked") @@ -47,7 +47,7 @@ final class QueryParameterOrBinder implements IParamBinder { public Object parse(List theString) throws InternalErrorException, InvalidRequestException { IQueryParameterOr dt; try { - dt = myType.newInstance(); + dt = newInstance(); if (theString.size() == 0 || theString.get(0).size() == 0) { return dt; } @@ -56,10 +56,6 @@ final class QueryParameterOrBinder implements IParamBinder { } dt.setValuesAsQueryTokens(theString.get(0)); - } catch (InstantiationException e) { - throw new InternalErrorException(e); - } catch (IllegalAccessException e) { - throw new InternalErrorException(e); } catch (SecurityException e) { throw new InternalErrorException(e); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java index d24a029b278..1b7b7c4f5e7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java @@ -31,11 +31,10 @@ import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -final class QueryParameterTypeBinder implements IParamBinder { - private final Class myType; +final class QueryParameterTypeBinder extends BaseBinder implements IParamBinder { - QueryParameterTypeBinder(Class theType) { - myType = theType; + QueryParameterTypeBinder(Class theType, Class[] theCompositeTypes) { + super(theType, theCompositeTypes); } @SuppressWarnings("unchecked") @@ -53,24 +52,16 @@ final class QueryParameterTypeBinder implements IParamBinder { return null; } - IQueryParameterType dt; - try { - dt = myType.newInstance(); - if (theParams.size() == 0 || theParams.get(0).size() == 0) { - return dt; - } - if (theParams.size() > 1 || theParams.get(0).size() > 1) { - throw new InvalidRequestException("Multiple values detected"); - } - - dt.setValueAsQueryToken(theParams.get(0).getQualifier(), value); - } catch (InstantiationException e) { - throw new InternalErrorException(e); - } catch (IllegalAccessException e) { - throw new InternalErrorException(e); - } catch (SecurityException e) { - throw new InternalErrorException(e); + IQueryParameterType dt = super.newInstance(); + + if (theParams.size() == 0 || theParams.get(0).size() == 0) { + return dt; } + if (theParams.size() > 1 || theParams.get(0).size() > 1) { + throw new InvalidRequestException("Multiple values detected"); + } + + dt.setValueAsQueryToken(theParams.get(0).getQualifier(), value); return dt; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java index 26705139867..6192a09957a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java @@ -39,6 +39,9 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.param.CodingListParam; +import ca.uhn.fhir.rest.param.CompositeAndListParam; +import ca.uhn.fhir.rest.param.CompositeOrListParam; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateAndListParam; import ca.uhn.fhir.rest.param.DateOrListParam; import ca.uhn.fhir.rest.param.DateParam; @@ -70,12 +73,46 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class SearchParameter extends BaseQueryParameter { private static HashMap, SearchParamTypeEnum> ourParamTypes; + static { + ourParamTypes = new HashMap, SearchParamTypeEnum>(); + + ourParamTypes.put(StringParam.class, SearchParamTypeEnum.STRING); + ourParamTypes.put(StringOrListParam.class, SearchParamTypeEnum.STRING); + ourParamTypes.put(StringAndListParam.class, SearchParamTypeEnum.STRING); + + ourParamTypes.put(TokenParam.class, SearchParamTypeEnum.TOKEN); + ourParamTypes.put(TokenOrListParam.class, SearchParamTypeEnum.TOKEN); + ourParamTypes.put(TokenAndListParam.class, SearchParamTypeEnum.TOKEN); + + ourParamTypes.put(DateParam.class, SearchParamTypeEnum.DATE); + ourParamTypes.put(DateOrListParam.class, SearchParamTypeEnum.DATE); + ourParamTypes.put(DateAndListParam.class, SearchParamTypeEnum.DATE); + ourParamTypes.put(DateRangeParam.class, SearchParamTypeEnum.DATE); + + ourParamTypes.put(QuantityParam.class, SearchParamTypeEnum.QUANTITY); + ourParamTypes.put(QuantityOrListParam.class, SearchParamTypeEnum.QUANTITY); + ourParamTypes.put(QuantityAndListParam.class, SearchParamTypeEnum.QUANTITY); + + ourParamTypes.put(NumberParam.class, SearchParamTypeEnum.NUMBER); + ourParamTypes.put(NumberOrListParam.class, SearchParamTypeEnum.NUMBER); + ourParamTypes.put(NumberAndListParam.class, SearchParamTypeEnum.NUMBER); + + ourParamTypes.put(ReferenceParam.class, SearchParamTypeEnum.REFERENCE); + ourParamTypes.put(ReferenceOrListParam.class, SearchParamTypeEnum.REFERENCE); + ourParamTypes.put(ReferenceAndListParam.class, SearchParamTypeEnum.REFERENCE); + + ourParamTypes.put(CompositeParam.class, SearchParamTypeEnum.COMPOSITE); + ourParamTypes.put(CompositeOrListParam.class, SearchParamTypeEnum.COMPOSITE); + ourParamTypes.put(CompositeAndListParam.class, SearchParamTypeEnum.COMPOSITE); + } + private Class[] myCompositeTypes; private Class[] myDeclaredTypes; private String myDescription; private String myName; private IParamBinder myParamBinder; private SearchParamTypeEnum myParamType; private boolean myRequired; + private Class myType; public SearchParameter() { @@ -150,8 +187,12 @@ public class SearchParameter extends BaseQueryParameter { return myParamBinder.parse(theString); } + public void setCompositeTypes(Class[] theCompositeTypes) { + myCompositeTypes = theCompositeTypes; + } + public void setDeclaredTypes(Class[] theTypes) { - myDeclaredTypes=theTypes; + myDeclaredTypes = theTypes; } public void setDescription(String theDescription) { @@ -166,57 +207,27 @@ public class SearchParameter extends BaseQueryParameter { this.myRequired = required; } - static { - ourParamTypes = new HashMap, SearchParamTypeEnum>(); - - ourParamTypes.put(StringParam.class, SearchParamTypeEnum.STRING); - ourParamTypes.put(StringOrListParam.class, SearchParamTypeEnum.STRING); - ourParamTypes.put(StringAndListParam.class, SearchParamTypeEnum.STRING); - - ourParamTypes.put(TokenParam.class, SearchParamTypeEnum.TOKEN); - ourParamTypes.put(TokenOrListParam.class, SearchParamTypeEnum.TOKEN); - ourParamTypes.put(TokenAndListParam.class, SearchParamTypeEnum.TOKEN); - - ourParamTypes.put(DateParam.class, SearchParamTypeEnum.DATE); - ourParamTypes.put(DateOrListParam.class, SearchParamTypeEnum.DATE); - ourParamTypes.put(DateAndListParam.class, SearchParamTypeEnum.DATE); - ourParamTypes.put(DateRangeParam.class, SearchParamTypeEnum.DATE); - - ourParamTypes.put(QuantityParam.class, SearchParamTypeEnum.QUANTITY); - ourParamTypes.put(QuantityOrListParam.class, SearchParamTypeEnum.QUANTITY); - ourParamTypes.put(QuantityAndListParam.class, SearchParamTypeEnum.QUANTITY); - - ourParamTypes.put(NumberParam.class, SearchParamTypeEnum.NUMBER); - ourParamTypes.put(NumberOrListParam.class, SearchParamTypeEnum.NUMBER); - ourParamTypes.put(NumberAndListParam.class, SearchParamTypeEnum.NUMBER); - - ourParamTypes.put(ReferenceParam.class, SearchParamTypeEnum.REFERENCE); - ourParamTypes.put(ReferenceOrListParam.class, SearchParamTypeEnum.REFERENCE); - ourParamTypes.put(ReferenceAndListParam.class, SearchParamTypeEnum.REFERENCE); - - } - @SuppressWarnings({ "unchecked" }) public void setType(final Class type, Class> theInnerCollectionType, Class> theOuterCollectionType) { this.myType = type; if (IQueryParameterType.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterTypeBinder((Class) type); + myParamBinder = new QueryParameterTypeBinder((Class) type, myCompositeTypes); } else if (IQueryParameterOr.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterOrBinder((Class>) type); + myParamBinder = new QueryParameterOrBinder((Class>) type,myCompositeTypes); } else if (IQueryParameterAnd.class.isAssignableFrom(type)) { - myParamBinder = new QueryParameterAndBinder((Class>) type); + myParamBinder = new QueryParameterAndBinder((Class>) type, myCompositeTypes); } else if (String.class.equals(type)) { myParamBinder = new StringBinder(); - myParamType=SearchParamTypeEnum.STRING; + myParamType = SearchParamTypeEnum.STRING; } else { throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName()); } - if (myParamType==null) { - myParamType=ourParamTypes.get(type); + if (myParamType == null) { + myParamType = ourParamTypes.get(type); } - - if (myParamType!=null) { + + if (myParamType != null) { // ok } else if (StringDt.class.isAssignableFrom(type)) { myParamType = SearchParamTypeEnum.STRING; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java index 0e86bd05d56..aff114e0684 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java @@ -26,8 +26,6 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.Set; -import org.apache.commons.lang3.StringUtils; - import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; @@ -42,10 +40,9 @@ import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { +class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { private Integer myIdParameterIndex; - private Integer myVersionIdParameterIndex; public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { @@ -112,13 +109,15 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe throw new NullPointerException("ID can not be null"); } - IdDt versionIdDt = null; if (myVersionIdParameterIndex != null) { - versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex]; + IdDt versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex]; + if (idDt.hasVersionIdPart() == false) { + idDt = idDt.withVersion(versionIdDt.getIdPart()); + } } FhirContext context = getContext(); - HttpPutClientInvocation retVal = createUpdateInvocation(theResource, idDt, versionIdDt, context); + HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null,idDt, context); for (int idx = 0; idx < theArgs.length; idx++) { IParameter nextParam = getParameters().get(idx); @@ -128,40 +127,7 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe return retVal; } - public static HttpPutClientInvocation createUpdateInvocation(IResource theResource, IdDt idDt, IdDt versionIdDt, FhirContext context) { - String resourceName = context.getResourceDefinition(theResource).getName(); - StringBuilder urlExtension = new StringBuilder(); - urlExtension.append(resourceName); - urlExtension.append('/'); - urlExtension.append(idDt.getIdPart()); - HttpPutClientInvocation retVal = new HttpPutClientInvocation(context, theResource, urlExtension.toString()); - - if (idDt.hasVersionIdPart()) { - String versionId = idDt.getVersionIdPart(); - if (StringUtils.isNotBlank(versionId)) { - StringBuilder b = new StringBuilder(); -// b.append('/'); - b.append(urlExtension); - b.append("/_history/"); - b.append(versionId); - retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString()); - } - } else if (versionIdDt != null) { - String versionId = versionIdDt.getValue(); - if (StringUtils.isNotBlank(versionId)) { - StringBuilder b = new StringBuilder(); -// b.append('/'); - b.append(urlExtension); - b.append("/_history/"); - b.append(versionId); - retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString()); - } - } - - MethodUtil.addTagsToPostOrPut(theResource, retVal); - - return retVal; - } + /* @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeAndListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeAndListParam.java new file mode 100644 index 00000000000..faadc968d39 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeAndListParam.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.rest.param; + +import ca.uhn.fhir.model.api.IQueryParameterType; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 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% + */ + + +public class CompositeAndListParam extends BaseAndListParam> { + + private Class myLeftType; + private Class myRightType; + + public CompositeAndListParam(Class theLeftType, Class theRightType) { + super(); + myLeftType = theLeftType; + myRightType = theRightType; + } + + @Override + CompositeOrListParam newInstance() { + return new CompositeOrListParam(myLeftType, myRightType); + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeOrListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeOrListParam.java new file mode 100644 index 00000000000..888462d19a0 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeOrListParam.java @@ -0,0 +1,43 @@ +package ca.uhn.fhir.rest.param; + +import ca.uhn.fhir.model.api.IQueryParameterType; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 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% + */ + + +public class CompositeOrListParam extends BaseOrListParam> { + + private Class myLeftType; + private Class myRightType; + + public CompositeOrListParam(Class theLeftType, Class theRightType) { + super(); + myLeftType = theLeftType; + myRightType = theRightType; + } + + @Override + CompositeParam newInstance() { + return new CompositeParam(myLeftType, myRightType); + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeParam.java new file mode 100644 index 00000000000..844ca9cff99 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CompositeParam.java @@ -0,0 +1,91 @@ +package ca.uhn.fhir.rest.param; + +import static org.apache.commons.lang3.StringUtils.*; + +import java.util.List; + +import org.apache.commons.lang3.Validate; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class CompositeParam implements IQueryParameterType { + + private A myLeftType; + private B myRightType; + + public CompositeParam(A theLeftInstance, B theRightInstance) { + myLeftType = theLeftInstance; + myRightType = theRightInstance; + } + + public CompositeParam(Class theLeftType, Class theRightType) { + Validate.notNull(theLeftType); + Validate.notNull(theRightType); + try { + myLeftType = theLeftType.newInstance(); + } catch (InstantiationException e) { + throw new ConfigurationException("Failed to instantiate type: " + myLeftType, e); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to instantiate type: " + myLeftType, e); + } + try { + myRightType = theRightType.newInstance(); + } catch (InstantiationException e) { + throw new ConfigurationException("Failed to instantiate type: " + myRightType, e); + } catch (IllegalAccessException e) { + throw new ConfigurationException("Failed to instantiate type: " + myRightType, e); + } + } + + /** + * @return Returns the left value for this parameter (the first of two parameters in this composite) + */ + public A getLeftValue() { + return myLeftType; + } + + @Override + public String getQueryParameterQualifier() { + return null; + } + + /** + * @return Returns the right value for this parameter (the second of two parameters in this composite) + */ + public B getRightValue() { + return myRightType; + } + + @Override + public String getValueAsQueryToken() { + StringBuilder b = new StringBuilder(); + if (myLeftType != null) { + b.append(myLeftType.getValueAsQueryToken()); + } + b.append('$'); + if (myRightType != null) { + b.append(myRightType.getValueAsQueryToken()); + } + return b.toString(); + } + + @Override + public void setValueAsQueryToken(String theQualifier, String theValue) { + if (isBlank(theValue)) { + myLeftType.setValueAsQueryToken(theQualifier, ""); + myRightType.setValueAsQueryToken(theQualifier, ""); + } else { + List parts = ParameterUtil.splitParameterString(theValue, '$', false); + if (parts.size() > 2) { + throw new InvalidRequestException("Invalid value for composite parameter (only one '$' is valid for this parameter, others must be escaped). Value was: " + theValue); + } + myLeftType.setValueAsQueryToken(theQualifier, parts.get(0)); + if (parts.size() > 1) { + myRightType.setValueAsQueryToken(theQualifier, parts.get(1)); + } + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java index a87366baf83..90e7c44a206 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateParam.java @@ -28,6 +28,7 @@ import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -162,4 +163,12 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery return b.toString(); } + public InstantDt getValueAsInstantDt() { + return new InstantDt(getValue()); + } + + public DateTimeDt getValueAsDateTimeDt() { + return new DateTimeDt(getValueAsString()); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java index 700192ae354..0b746513662 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ParameterUtil.java @@ -52,23 +52,21 @@ public class ParameterUtil { } - - // public static Integer findSinceParameterIndex(Method theMethod) { // return findParamIndex(theMethod, Since.class); // } public static int nonEscapedIndexOf(String theString, char theCharacter) { - for (int i =0; i < theString.length(); i++) { - if (theString.charAt(i)==theCharacter) { - if (i == 0 || theString.charAt(i-1) != '\\') { + for (int i = 0; i < theString.length(); i++) { + if (theString.charAt(i) == theCharacter) { + if (i == 0 || theString.charAt(i - 1) != '\\') { return i; } } } return -1; } - + public static Object fromInstant(Class theType, InstantDt theArgument) { if (theType.equals(InstantDt.class)) { if (theArgument == null) { @@ -138,19 +136,28 @@ public class ParameterUtil { return null; } - public static List splitParameterString(String theInput, boolean theUnescapeComponents){ + static List splitParameterString(String theInput, boolean theUnescapeComponents) { + return splitParameterString(theInput, ',', theUnescapeComponents); + } + + static List splitParameterString(String theInput, char theDelimiter, boolean theUnescapeComponents) { ArrayList retVal = new ArrayList(); - if (theInput!=null) { + if (theInput != null) { StringBuilder b = new StringBuilder(); for (int i = 0; i < theInput.length(); i++) { char next = theInput.charAt(i); - if (next == ',') { - if (i == 0 || theInput.charAt(i-1) != '\\') { + if (next == theDelimiter) { + if (i == 0) { b.append(next); } else { - if (b.length() > 0) { - retVal.add(b.toString()); - b.setLength(0); + char prevChar = theInput.charAt(i - 1); + if (prevChar == '\\') { + b.append(next); + } else { + if (b.length() > 0) { + retVal.add(b.toString()); + b.setLength(0); + } } } } else { @@ -161,28 +168,27 @@ public class ParameterUtil { retVal.add(b.toString()); } } - + if (theUnescapeComponents) { - for (int i = 0; i < retVal.size();i++) { - retVal.set(i,unescape(retVal.get(i))); + for (int i = 0; i < retVal.size(); i++) { + retVal.set(i, unescape(retVal.get(i))); } } - + return retVal; } - - + /** - * Escapes a string according to the rules for parameter escaping specified - * in the FHIR Specification Escaping Section + * Escapes a string according to the rules for parameter escaping specified in the FHIR Specification Escaping Section */ public static String escape(String theValue) { if (theValue == null) { return theValue; } StringBuilder b = new StringBuilder(); - - for (int i = 0; i < theValue.length();i++) { + + for (int i = 0; i < theValue.length(); i++) { char next = theValue.charAt(i); switch (next) { case '$': @@ -194,31 +200,31 @@ public class ParameterUtil { b.append(next); } } - + return b.toString(); } /** - * Unescapes a string according to the rules for parameter escaping specified - * in the FHIR Specification Escaping Section + * Unescapes a string according to the rules for parameter escaping specified in the FHIR Specification Escaping Section */ public static String unescape(String theValue) { if (theValue == null) { return theValue; } - if (theValue.indexOf('\\')==-1) { + if (theValue.indexOf('\\') == -1) { return theValue; } StringBuilder b = new StringBuilder(); - - for (int i = 0; i < theValue.length();i++) { + + for (int i = 0; i < theValue.length(); i++) { char next = theValue.charAt(i); if (next == '\\') { - if (i == theValue.length()-1) { + if (i == theValue.length() - 1) { b.append(next); } else { - switch (theValue.charAt(i+1)) { + switch (theValue.charAt(i + 1)) { case '$': case ',': case '|': @@ -231,7 +237,7 @@ public class ParameterUtil { b.append(next); } } - + return b.toString(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringParam.java index 7fdceff7faf..02b0f7785c0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringParam.java @@ -27,6 +27,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.server.Constants; public class StringParam implements IQueryParameterType { @@ -61,7 +62,7 @@ public class StringParam implements IQueryParameterType { @Override public String getValueAsQueryToken() { - return myValue; + return ParameterUtil.escape(myValue); } public String getValueNotNull() { @@ -91,7 +92,7 @@ public class StringParam implements IQueryParameterType { } else { setExact(false); } - myValue = theValue; + myValue = ParameterUtil.unescape(theValue); } @Override @@ -104,4 +105,8 @@ public class StringParam implements IQueryParameterType { return builder.toString(); } + public StringDt getValueAsStringDt() { + return new StringDt(myValue); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java index 7b8a8a42a45..481538011ba 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenOrListParam.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import ca.uhn.fhir.model.dstu.composite.CodingDt; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; /* * #%L @@ -44,8 +45,20 @@ public class TokenOrListParam extends BaseOrListParam { return retVal; } + /** + * Convenience method which adds a token to this OR list + * using the system and code from a coding + */ public void add(CodingDt theCodingDt) { add(new TokenParam(theCodingDt)); } + /** + * Convenience method which adds a token to this OR list + * using the system and value from an identifier + */ + public void add(IdentifierDt theIdentifierDt) { + add(new TokenParam(theIdentifierDt)); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java index 4be61691a67..8f072e299a1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TokenParam.java @@ -28,6 +28,8 @@ import org.apache.commons.lang3.builder.ToStringStyle; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu.composite.CodingDt; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; +import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.server.Constants; public class TokenParam implements IQueryParameterType { @@ -61,7 +63,21 @@ public class TokenParam implements IQueryParameterType { * @param theCodingDt The coding */ public TokenParam(CodingDt theCodingDt) { - this(theCodingDt.getSystem().getValue().toASCIIString(), theCodingDt.getCode().getValue()); + this(toSystemValue(theCodingDt.getSystem()), theCodingDt.getCode().getValue()); + } + + /** + * Constructor which copies the {@link IdentifierDt#getSystem() system} and {@link IdentifierDt#getValue() value} from a {@link IdentifierDt} + * instance and adds it as a parameter + * + * @param theCodingDt The coding + */ + public TokenParam(IdentifierDt theIdentifierDt) { + this(toSystemValue(theIdentifierDt.getSystem()), theIdentifierDt.getValue().getValue()); + } + + private static String toSystemValue(UriDt theSystem) { + return theSystem.getValueAsString(); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index 76d4f03414e..9814223c2d6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -418,7 +418,7 @@ public class RestfulServer extends HttpServlet { IdDt versionId = null; String operation = null; - String requestPath = requestFullPath.substring(servletContextPath.length() + servletPath.length()); + String requestPath = requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath)); if (requestPath.length() > 0 && requestPath.charAt(0) == '/') { requestPath = requestPath.substring(1); } @@ -599,6 +599,20 @@ public class RestfulServer extends HttpServlet { } + /** + * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20) + */ + private int escapedLength(String theServletPath) { + int delta = 0; + for(int i =0;i childElementDef = nextChild.getChildElementDefinitionByDatatype(nextValue.getClass()); if (childElementDef == null) { - throw new DataFormatException("Found value of type[" + nextValue.getClass().getSimpleName() + "] which is not valid for field[" + nextChild.getElementName() + "] in " + childDef.getName()); + StringBuilder b = new StringBuilder(); + b.append("Found value of type["); + b.append(nextValue.getClass().getSimpleName()); + b.append("] which is not valid for field["); + b.append(nextChild.getElementName()); + b.append("] in "); + b.append(childDef.getName()); + b.append(" - Valid types: "); + for (Iterator iter = new TreeSet(nextChild.getValidChildNames()).iterator(); iter.hasNext();) { + b.append(nextChild.getChildByName(iter.next()).getImplementingClass().getSimpleName()); + if (iter.hasNext()) { + b.append(", "); + } + } + throw new DataFormatException(b.toString()); } getAllChildElementsOfType(nextValue, childElementDef, theType, theList); } diff --git a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java index c0fd6fe8f50..0e70994bd67 100644 --- a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java +++ b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java @@ -7,6 +7,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; @@ -55,6 +56,25 @@ client.create() //END SNIPPET: create } { +//START SNIPPET: update +Patient patient = new Patient(); +// ..populate the patient object.. +patient.addIdentifier("urn:system", "12345"); +patient.addName().addFamily("Smith").addGiven("John"); + +// To update a resource, it should have an ID set (if the resource object +// comes from the results of a previous read or search, it will already +// have one though) +patient.setId("Patient/123"); + +// Invoke the server create method (and send pretty-printed JSON encoding to the server +// instead of the default which is non-pretty printed XML) +client.update() + .resource(patient) + .execute(); +//END SNIPPET: update +} +{ //START SNIPPET: conformance // Retrieve the server's conformance statement and print its description Conformance conf = client.conformance(); @@ -97,6 +117,16 @@ response = client.search() .execute(); //END SNIPPET: searchAdv +//START SNIPPET: searchComposite +response = client.search() + .forResource("Observation") + .where(Observation.NAME_VALUE_DATE + .withLeft(Observation.NAME.exactly().code("FOO$BAR")) + .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01")) + ) + .execute(); +//END SNIPPET: searchComposite + //START SNIPPET: searchPaging if (response.getLinkNext().isEmpty() == false) { diff --git a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java index 8d9f52a7458..6db2c71a892 100644 --- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java +++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java @@ -60,6 +60,7 @@ import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.client.ITestClient; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QuantityParam; @@ -285,6 +286,24 @@ public List searchByNamedQuery(@RequiredParam(name="someparam") StringP } //END SNIPPET: searchNamedQuery +//START SNIPPET: searchComposite +@Search() +public List searchByComposite( + @RequiredParam(name=Observation.SP_NAME_VALUE_DATE, compositeTypes= {TokenParam.class, DateParam.class}) + CompositeParam theParam) { + // Each of the two values in the composite param are accessible separately. + // In the case of Observation's name-value-date, the left is a string and + // the right is a date. + TokenParam observationName = theParam.getLeftValue(); + DateParam observationValue = theParam.getRightValue(); + + List retVal = new ArrayList(); + // ...populate... + return retVal; +} +//END SNIPPET: searchComposite + + //START SNIPPET: searchIdentifierParam @Search() public List searchByIdentifier(@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theId) { diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml index 3dbb9b6f201..2df450991e4 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml @@ -89,6 +89,7 @@ +

The following example shows how to query using the generic client:

@@ -97,6 +98,8 @@ + +

Search - Paging

If the server supports paging results, the client has a page method which can be used to load subsequent pages. @@ -106,6 +109,21 @@ + +

Search - Composite Parameters

+

+ If a composite parameter is being searched on, the parameter + takes a "left" and "right" operand, each of which is + a parameter from the resource being seached. The following example shows the + syntax. +

+ + + + + +

Search - Query Options

The fluent search also has methods for sorting, limiting, specifying JSON encoding, etc. @@ -115,6 +133,7 @@ + @@ -129,6 +148,23 @@ + +

+ Updating a resource is similar to creating one, except that + an ID must be supplied since you are updating a previously + existing resource instance. +

+

+ The following example shows how to perform an update + operation using the generic client: +

+ + + + +
+

To retrieve the server's conformance statement, simply call the conformance() @@ -157,7 +193,7 @@

- HAPI also provides a second style of client caled the annotation-driven client. + HAPI also provides a second style of client, called the annotation-driven client.

diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml index 95398292051..0cacc1411c4 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml @@ -727,6 +727,34 @@ + + +

+ Composite search parameters incorporate two parameters in a single + value. Each of those parameters will themselves have a parameter type. +

+ +

+ In the following example, Observation.name-value-date is shown. This parameter + is a composite of a string and a date. Note that the composite parameter types + (StringParam and DateParam) must be specified in both the annotation's + compositeTypes field, as well as the generic types for the + CompositeParam method parameter itself. +

+ + + + + + +

+ Example URL to invoke this method: +
+ http://fhir.example.com/Observation?name-value-date=PROCTIME$2001-02-02 +

+ + +

diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java index 0e0baf6f5df..493258f0af8 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java @@ -10,7 +10,6 @@ import org.junit.Before; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt; diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java index 405342fdd87..64532dad613 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -28,7 +28,6 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; -import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.Child; @@ -113,8 +112,11 @@ public class JsonParserTest { " }" + "}"; //@formatter:on - IResource res = ourCtx.newJsonParser().parseResource(text); + Patient res = (Patient) ourCtx.newJsonParser().parseResource(text); + String value = res.getText().getDiv().getValueAsString(); + + assertNull(value); } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java index e8f9a8996fa..0266537d7fa 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import java.io.InputStream; import java.io.StringReader; +import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Date; @@ -45,6 +46,7 @@ import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; @@ -58,6 +60,7 @@ import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QuantityParam; @@ -771,7 +774,7 @@ public class ClientTest { } @Test - public void testSearchComposite() throws Exception { + public void testSearchOrList() throws Exception { String msg = getPatientFeedWithOneResult(); @@ -944,6 +947,28 @@ public class ClientTest { assertEquals("PRP1660", resource.getIdentifier().get(0).getValue().getValue()); } + + + + @Test + public void testSearchByCompositeParam() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + StringParam str = new StringParam("FOO$BAR"); + DateParam date = new DateParam("2001-01-01"); + client.getObservationByNameValueDate(new CompositeParam(str, date)); + + assertEquals("http://foo/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01","UTF-8"), capt.getValue().getURI().toString()); + + } @Test public void testSearchWithStringIncludes() throws Exception { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index efc39fb206c..66d86cc3e3b 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.StringReader; +import java.net.URLEncoder; import java.nio.charset.Charset; import java.util.Arrays; @@ -15,6 +16,7 @@ import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; @@ -41,6 +43,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class GenericClientTest { @@ -138,6 +141,68 @@ public class GenericClientTest { } + @Test + public void testUpdate() throws Exception { + + Patient p1 = new Patient(); + p1.addIdentifier("foo:bar", "12345"); + p1.addName().addFamily("Smith").addGiven("John"); + TagList list = new TagList(); + list.addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); + ResourceMetadataKeyEnum.TAG_LIST.put(p1, list); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.update().resource(p1).execute(); + fail(); + } catch (InvalidRequestException e) { + // should happen because no ID set + } + + assertEquals(0, capt.getAllValues().size()); + + p1.setId("44"); + client.update().resource(p1).execute(); + + assertEquals(1, capt.getAllValues().size()); + + MethodOutcome outcome = client.update().resource(p1).execute(); + assertEquals("44", outcome.getId().getIdPart()); + assertEquals("22", outcome.getId().getVersionIdPart()); + + assertEquals(2, capt.getAllValues().size()); + + assertEquals("http://example.com/fhir/Patient/44", capt.getValue().getURI().toString()); + assertEquals("PUT", capt.getValue().getMethod()); + Header catH = capt.getValue().getFirstHeader("Category"); + assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH); + assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue()); + + /* + * Try fluent options + */ + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.update().resource(p1).withId("123").execute(); + assertEquals(3, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(2).getURI().toString()); + + String resourceText = " "; + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + client.update().resource(resourceText).withId("123").execute(); + assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(3).getURI().toString()); + assertEquals(resourceText, IOUtils.toString(((HttpPut)capt.getAllValues().get(3)).getEntity().getContent())); + assertEquals(4, capt.getAllValues().size()); + + } + @Test public void testDelete() throws Exception { OperationOutcome oo = new OperationOutcome(); @@ -362,6 +427,45 @@ public class GenericClientTest { assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString()); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + //@formatter:off + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().code("ZZZ")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?identifier=ZZZ", capt.getAllValues().get(1).getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByComposite() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); + + //@formatter:off + Bundle response = client.search() + .forResource("Observation") + .where(Observation.NAME_VALUE_DATE + .withLeft(Observation.NAME.exactly().code("FOO$BAR")) + .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01")) + ) + .execute(); + //@formatter:on + + assertEquals("http://foo/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01","UTF-8"), capt.getValue().getURI().toString()); + } @Test diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java index 79de6fd4650..a2408694db3 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java @@ -6,6 +6,7 @@ import java.util.List; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; @@ -26,6 +27,7 @@ import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QuantityParam; @@ -40,7 +42,10 @@ public interface ITestClient extends IBasicClient { @Search() public List getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers); - + + @Search(type=Observation.class) + public Bundle getObservationByNameValueDate(@RequiredParam(name = Observation.SP_NAME_VALUE_DATE, compositeTypes= {StringParam.class,DateParam.class}) CompositeParam theIdentifiers); + @Search() public List getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theBirthDate); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java new file mode 100644 index 00000000000..49ad18a1e67 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CompositeParameterTest.java @@ -0,0 +1,178 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.*; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpResponse; +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.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.CompositeAndListParam; +import ca.uhn.fhir.rest.param.CompositeOrListParam; +import ca.uhn.fhir.rest.param.CompositeParam; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.testutil.RandomServerPortProvider; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class CompositeParameterTest { + + private static CloseableHttpClient ourClient; + private static FhirContext ourCtx; + private static int ourPort; + private static Server ourServer; + + @Test + public void testSearchWithDateValue() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("foo\\$bar$2001-01-01", "UTF-8")); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = ourCtx.newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Observation o = (Observation) entries.get(0).getResource(); + assertEquals("foo$bar", o.getName().getText().getValue()); + assertEquals("2001-01-01", ((DateTimeDt) o.getApplies()).getValueAsString().substring(0, 10)); + } + } + + @Test + public void testSearchWithMultipleValue() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Observation?" + Observation.SP_NAME_VALUE_STRING + "=" + URLEncoder.encode("l1$r1,l2$r2", "UTF-8") + "&" + Observation.SP_NAME_VALUE_STRING + "=" + URLEncoder.encode("l3$r3,l4$r4", "UTF-8")); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = ourCtx.newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Observation o = (Observation) entries.get(0).getResource(); + assertEquals("AND", o.getName().getCoding().get(0).getDisplay().getValue()); + assertEquals("OR", o.getName().getCoding().get(1).getDisplay().getValue()); + assertEquals("l1", o.getName().getCoding().get(1).getSystem().getValueAsString()); + assertEquals("r1", o.getName().getCoding().get(1).getCode().getValue()); + assertEquals("OR", o.getName().getCoding().get(2).getDisplay().getValue()); + assertEquals("l2", o.getName().getCoding().get(2).getSystem().getValueAsString()); + assertEquals("r2", o.getName().getCoding().get(2).getCode().getValue()); + + assertEquals("AND", o.getName().getCoding().get(3).getDisplay().getValue()); + assertEquals("OR", o.getName().getCoding().get(4).getDisplay().getValue()); + assertEquals("l3", o.getName().getCoding().get(4).getSystem().getValueAsString()); + assertEquals("r3", o.getName().getCoding().get(4).getCode().getValue()); + assertEquals("OR", o.getName().getCoding().get(5).getDisplay().getValue()); + assertEquals("l4", o.getName().getCoding().get(5).getSystem().getValueAsString()); + assertEquals("r4", o.getName().getCoding().get(5).getCode().getValue()); + } + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(ourPort); + + DummyObservationResourceProvider patientProvider = new DummyObservationResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + ourCtx = servlet.getFhirContext(); + servlet.setResourceProviders(patientProvider); + 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(); + + ourCtx = servlet.getFhirContext(); + } + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class DummyObservationResourceProvider implements IResourceProvider { + //@formatter:off + @Search + public List findObservation( + @RequiredParam(name = Observation.SP_NAME_VALUE_DATE, compositeTypes= { StringParam.class, DateParam.class }) + CompositeParam theParam + ) { + //@formatter:on + + ArrayList retVal = new ArrayList(); + + Observation p = new Observation(); + p.setId("1"); + p.setApplies(theParam.getRightValue().getValueAsDateTimeDt()); + p.getName().setText(theParam.getLeftValue().getValueAsStringDt()); + retVal.add(p); + + return retVal; + } + + //@formatter:off + @Search + public List findObservationNVS( + @RequiredParam(name = Observation.SP_NAME_VALUE_STRING, compositeTypes= { StringParam.class, StringParam.class }) + CompositeAndListParam theParam + ) { + //@formatter:on + + ArrayList retVal = new ArrayList(); + + Observation p = new Observation(); + p.setId("1"); + for (CompositeOrListParam nextAnd : theParam.getValuesAsQueryTokens()) { + p.getName().addCoding().getDisplay().setValue("AND"); + for (CompositeParam nextOr : nextAnd.getValuesAsQueryTokens()) { + p.getName().addCoding().setDisplay("OR").setSystem(nextOr.getLeftValue().getValue()).setCode(nextOr.getRightValue().getValue()); + } + } + retVal.add(p); + + return retVal; + } + + @Override + public Class getResourceType() { + return Observation.class; + } + + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerSelfReferenceTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerSelfReferenceTest.java index f3e2734ef4a..737d22f82c1 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerSelfReferenceTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerSelfReferenceTest.java @@ -2,8 +2,6 @@ package ca.uhn.fhir.rest.server; import static org.junit.Assert.*; -import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -15,9 +13,10 @@ 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.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -42,89 +41,103 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider; public class ResfulServerSelfReferenceTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResfulServerSelfReferenceTest.class); - private static int ourPort; - private static Server ourServer; private static CloseableHttpClient ourClient; private static FhirContext ourCtx; @BeforeClass public static void beforeClass() throws Exception { - ourPort = RandomServerPortProvider.findFreePort(); - ourServer = new Server(ourPort); ourCtx = new FhirContext(Patient.class); - DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); - ServerProfileProvider profProvider=new ServerProfileProvider(ourCtx); - - ServletHandler proxyHandler = new ServletHandler(); - ServletHolder servletHolder = new ServletHolder(new DummyRestfulServer(patientProvider,profProvider)); - proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*"); - ourServer.setHandler(proxyHandler); - ourServer.start(); - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); HttpClientBuilder builder = HttpClientBuilder.create(); builder.setConnectionManager(connectionManager); ourClient = builder.build(); - - } - @AfterClass - public static void afterClass() throws Exception { - ourServer.stop(); - } + @Test + public void testContextWithSpace() throws Exception { + int port = RandomServerPortProvider.findFreePort(); + Server server = new Server(port); + RestfulServer restServer = new RestfulServer(); + restServer.setFhirContext(ourCtx); + restServer.setResourceProviders(new DummyPatientResourceProvider()); + + // ServletHandler proxyHandler = new ServletHandler(); + ServletHolder servletHolder = new ServletHolder(restServer); + + ServletContextHandler ch = new ServletContextHandler(); + ch.setContextPath("/root ctx/rcp2"); + ch.addServlet(servletHolder, "/fhir ctx/fcp2/*"); + + ContextHandlerCollection contexts = new ContextHandlerCollection(); + server.setHandler(contexts); + + server.setHandler(ch); + server.start(); + try { + + String baseUri = "http://localhost:" + port + "/root%20ctx/rcp2/fhir%20ctx/fcp2"; + String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001"; + HttpGet httpGet = new HttpGet(uri); + HttpResponse status = ourClient.execute(httpGet); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + + assertEquals(1, bundle.getEntries().size()); + + } finally { + server.stop(); + } + + } @Test public void testSearchByParamIdentifier() throws Exception { + int port = RandomServerPortProvider.findFreePort(); + Server hServer = new Server(port); - String baseUri = "http://localhost:" + ourPort + "/fhir/context"; - String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001"; - HttpGet httpGet = new HttpGet(uri); - HttpResponse status = ourClient.execute(httpGet); + DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); + ServerProfileProvider profProvider = new ServerProfileProvider(ourCtx); - String responseContent = IOUtils.toString(status.getEntity().getContent()); - ourLog.info("Response was:\n{}", responseContent); + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer server = new RestfulServer(); + server.setFhirContext(ourCtx); + server.setResourceProviders(patientProvider, profProvider); + ServletHolder servletHolder = new ServletHolder(server); + proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*"); + hServer.setHandler(proxyHandler); + hServer.start(); + try { + String baseUri = "http://localhost:" + port + "/fhir/context"; + String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001"; + HttpGet httpGet = new HttpGet(uri); + HttpResponse status = ourClient.execute(httpGet); - assertEquals(200, status.getStatusLine().getStatusCode()); - Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); - assertEquals(1, bundle.getEntries().size()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); - Patient patient = (Patient) bundle.getEntries().get(0).getResource(); - assertEquals("PatientOne", patient.getName().get(0).getGiven().get(0).getValue()); + assertEquals(1, bundle.getEntries().size()); - assertEquals(uri, bundle.getLinkSelf().getValue()); - assertEquals(baseUri, bundle.getLinkBase().getValue()); + Patient patient = (Patient) bundle.getEntries().get(0).getResource(); + assertEquals("PatientOne", patient.getName().get(0).getGiven().get(0).getValue()); + + assertEquals(uri, bundle.getLinkSelf().getValue()); + assertEquals(baseUri, bundle.getLinkBase().getValue()); + } finally { + hServer.stop(); + } } - - - - public static class DummyRestfulServer extends RestfulServer { - - private static final long serialVersionUID = 1L; - - private Collection myResourceProviders; - - public DummyRestfulServer(IResourceProvider... theResourceProviders) { - myResourceProviders = Arrays.asList(theResourceProviders); - } - - @Override - public Collection getResourceProviders() { - return myResourceProviders; - } - - @Override - public ISecurityManager getSecurityManager() { - return null; - } - - } - - /** * Created by dsotnikov on 2/25/2014. */ @@ -161,8 +174,6 @@ public class ResfulServerSelfReferenceTest { return idToPatient; } - - @Search() public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { for (Patient next : getIdToPatient().values()) { @@ -174,8 +185,7 @@ public class ResfulServerSelfReferenceTest { } return null; } - - + /** * Retrieve the resource by its identifier * @@ -195,6 +205,4 @@ public class ResfulServerSelfReferenceTest { } - - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index 9c9ec1296d9..2c905b7540d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -7,7 +7,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -22,6 +24,9 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Order; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; @@ -69,8 +74,9 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.NumberParam; @@ -80,12 +86,12 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.FhirTerser; -import example.QuickUsage.MyClientInterface; @Transactional(propagation = Propagation.REQUIRED) public class FhirResourceDao extends BaseFhirDao implements IFhirResourceDao { @@ -102,6 +108,688 @@ public class FhirResourceDao extends BaseFhirDao implements private Class myResourceType; private String mySecondaryPrimaryKeyParamName; + @Override + public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) { + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + + for (BaseTag next : new ArrayList(entity.getTags())) { + if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { + return; + } + } + + entity.setHasTags(true); + + TagDefinition def = getTag(theScheme, theTerm, theLabel); + BaseTag newEntity = entity.addTag(def); + + myEntityManager.persist(newEntity); + myEntityManager.merge(entity); + notifyWriteCompleted(); + ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); + } + + @Override + public MethodOutcome create(final T theResource) { + StopWatch w = new StopWatch(); + ResourceTable entity = new ResourceTable(); + entity.setResourceType(toResourceName(theResource)); + + if (theResource.getId().isEmpty() == false) { + if (isValidPid(theResource.getId())) { + throw new UnprocessableEntityException("This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); + } + createForcedIdIfNeeded(entity, theResource.getId()); + + if (entity.getForcedId() != null) { + try { + translateForcedIdToPid(theResource.getId()); + throw new UnprocessableEntityException("Can not create entity with ID[" + theResource.getId().getValue() + "], constraint violation occurred"); + } catch (ResourceNotFoundException e) { + // good, this ID doesn't exist so we can create it + } + } + + } + + updateEntity(theResource, entity, false, false); + + MethodOutcome outcome = toMethodOutcome(entity); + notifyWriteCompleted(); + ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return outcome; + } + + @Override + public MethodOutcome delete(IdDt theId) { + StopWatch w = new StopWatch(); + final ResourceTable entity = readEntityLatestVersion(theId); + if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { + throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); + } + + ResourceTable savedEntity = updateEntity(null, entity, true, true); + + notifyWriteCompleted(); + + ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); + return toMethodOutcome(savedEntity); + } + + // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { + // } + + @Override + public TagList getAllResourceTags() { + StopWatch w = new StopWatch(); + TagList tags = super.getTags(myResourceType, null); + ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return tags; + } + + public Class getResourceType() { + return myResourceType; + } + + @Override + public TagList getTags(IdDt theResourceId) { + StopWatch w = new StopWatch(); + TagList retVal = super.getTags(myResourceType, theResourceId); + ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); + return retVal; + } + + @Override + public IBundleProvider history(Date theSince) { + StopWatch w = new StopWatch(); + IBundleProvider retVal = super.history(myResourceName, null, theSince); + ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); + return retVal; + } + + @Override + public IBundleProvider history(final IdDt theId, final Date theSince) { + final InstantDt end = createHistoryToTimestamp(); + final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); + + T currentTmp; + try { + currentTmp = read(theId.toVersionless()); + if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { + currentTmp = null; + } + } catch (ResourceNotFoundException e) { + currentTmp = null; + } + + final T current = currentTmp; + + String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); + TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); + countQuery.setParameter("PID", theId.getIdPartAsLong()); + countQuery.setParameter("RESTYPE", resourceType); + countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); + if (theSince != null) { + countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); + } + int historyCount = countQuery.getSingleResult().intValue(); + + final int offset; + final int count; + if (current != null) { + count = historyCount + 1; + offset = 1; + } else { + offset = 0; + count = historyCount; + } + + if (count == 0) { + throw new ResourceNotFoundException(theId); + } + + return new IBundleProvider() { + + @Override + public InstantDt getPublished() { + return end; + } + + @Override + public List getResources(int theFromIndex, int theToIndex) { + ArrayList retVal = new ArrayList(); + if (theFromIndex == 0 && current != null) { + retVal.add(current); + } + + TypedQuery q = myEntityManager.createQuery("SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); + q.setParameter("PID", theId.getIdPartAsLong()); + q.setParameter("RESTYPE", resourceType); + q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); + if (theSince != null) { + q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); + } + + q.setFirstResult(Math.max(0, theFromIndex - offset)); + q.setMaxResults(theToIndex - theFromIndex); + + List results = q.getResultList(); + for (ResourceHistoryTable next : results) { + if (retVal.size() == (theToIndex - theFromIndex)) { + break; + } + retVal.add(toResource(myResourceType, next)); + } + + return retVal; + } + + @Override + public int size() { + return count; + } + }; + + } + + @Override + public IBundleProvider history(Long theId, Date theSince) { + StopWatch w = new StopWatch(); + IBundleProvider retVal = super.history(myResourceName, theId, theSince); + ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); + return retVal; + } + + @PostConstruct + public void postConstruct() { + RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); + myResourceName = def.getName(); + + if (mySecondaryPrimaryKeyParamName != null) { + RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); + if (sp == null) { + throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); + } + if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { + throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported"); + } + } + + } + + @Override + public T read(IdDt theId) { + validateResourceTypeAndThrowIllegalArgumentException(theId); + + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + validateResourceType(entity); + + T retVal = toResource(myResourceType, entity); + + InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); + if (deleted != null && !deleted.isEmpty()) { + throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); + } + + ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); + return retVal; + } + + @Override + public BaseHasResource readEntity(IdDt theId) { + validateResourceTypeAndThrowIllegalArgumentException(theId); + + Long pid = translateForcedIdToPid(theId); + BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); + if (theId.hasVersionIdPart()) { + if (entity.getVersion() != theId.getVersionIdPartAsLong()) { + entity = null; + } + } + + if (entity == null) { + if (theId.hasVersionIdPart()) { + TypedQuery q = myEntityManager.createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); + q.setParameter("RID", theId.getIdPartAsLong()); + q.setParameter("RTYP", myResourceName); + q.setParameter("RVER", theId.getVersionIdPartAsLong()); + entity = q.getSingleResult(); + } + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + } + + validateResourceType(entity); + + return entity; + } + + @Override + public void removeTag(IdDt theId, String theScheme, String theTerm) { + StopWatch w = new StopWatch(); + BaseHasResource entity = readEntity(theId); + if (entity == null) { + throw new ResourceNotFoundException(theId); + } + + for (BaseTag next : new ArrayList(entity.getTags())) { + if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { + myEntityManager.remove(next); + entity.getTags().remove(next); + } + } + + if (entity.getTags().isEmpty()) { + entity.setHasTags(false); + } + + myEntityManager.merge(entity); + + ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); + } + + @Override + public IBundleProvider search(Map theParams) { + SearchParameterMap map = new SearchParameterMap(); + for (Entry nextEntry : theParams.entrySet()) { + map.add(nextEntry.getKey(), (nextEntry.getValue())); + } + return search(map); + } + + @Override + public IBundleProvider search(final SearchParameterMap theParams) { + StopWatch w = new StopWatch(); + final InstantDt now = InstantDt.withCurrentTime(); + + Set loadPids; + if (theParams.isEmpty()) { + loadPids = new HashSet(); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceTable.class); + cq.multiselect(from.get("myId").as(Long.class)); + cq.where(builder.equal(from.get("myResourceType"), myResourceName)); + + TypedQuery query = myEntityManager.createQuery(cq); + for (Tuple next : query.getResultList()) { + loadPids.add(next.get(0, Long.class)); + } + } else { + loadPids = searchForIdsWithAndOr(theParams); + if (loadPids.isEmpty()) { + return new SimpleBundleProvider(); + } + } + + final List pids = new ArrayList(loadPids); + + // Handle sorting if any was provided + if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { + List orders = new ArrayList(); + List predicates = new ArrayList(); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createTupleQuery(); + Root from = cq.from(ResourceTable.class); + predicates.add(from.get("myId").in(pids)); + createSort(builder, from, theParams.getSort(), orders,predicates); + if (orders.size() > 0) { + loadPids = new LinkedHashSet(); + cq.multiselect(from.get("myId").as(Long.class)); + cq.where(predicates.toArray(new Predicate[0])); + cq.orderBy(orders); + + TypedQuery query = myEntityManager.createQuery(cq); + + for (Tuple next : query.getResultList()) { + loadPids.add(next.get(0, Long.class)); + } + + ourLog.info("Sort PID order is now: {}", loadPids); + + pids.clear(); + pids.addAll(loadPids); + } + } + + IBundleProvider retVal = new IBundleProvider() { + + @Override + public InstantDt getPublished() { + return now; + } + + @Override + public List getResources(final int theFromIndex, final int theToIndex) { + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + return template.execute(new TransactionCallback>() { + @Override + public List doInTransaction(TransactionStatus theStatus) { + List pidsSubList = pids.subList(theFromIndex, theToIndex); + + // Execute the query and make sure we return distinct results + List retVal = new ArrayList(); + loadResourcesByPid(pidsSubList, retVal); + + // Load _include resources + if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { + Set previouslyLoadedPids = new HashSet(); + + Set includePids = new HashSet(); + List resources = retVal; + do { + includePids.clear(); + + FhirTerser t = getContext().newTerser(); + for (Include next : theParams.getIncludes()) { + for (IResource nextResource : resources) { + RuntimeResourceDefinition def = getContext().getResourceDefinition(nextResource); + if (!next.getValue().startsWith(def.getName() + ".")) { + continue; + } + + List values = t.getValues(nextResource, next.getValue()); + for (Object object : values) { + if (object == null) { + continue; + } + if (!(object instanceof ResourceReferenceDt)) { + throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); + } + ResourceReferenceDt rr = (ResourceReferenceDt) object; + if (rr.getReference().isEmpty()) { + continue; + } + if (rr.getReference().isLocal()) { + continue; + } + + IdDt nextId = rr.getReference().toUnqualified(); + if (!previouslyLoadedPids.contains(nextId)) { + includePids.add(nextId); + previouslyLoadedPids.add(nextId); + } + } + } + } + + if (!includePids.isEmpty()) { + ourLog.info("Loading {} included resources", includePids.size()); + resources = loadResourcesById(includePids); + retVal.addAll(resources); + } + } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); + + if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); + retVal.add(0, oo); + } + } + + return retVal; + } + }); + } + + @Override + public int size() { + return pids.size(); + } + }; + + ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); + + return retVal; + } + + private void createSort(CriteriaBuilder theBuilder, Root theFrom, SortSpec theSort, List theOrders, List thePredicates) { + if (theSort==null||isBlank(theSort.getParamName())) { + return; + } + + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); + RuntimeSearchParam param = resourceDef.getSearchParam(theSort.getParamName()); + if (param == null) { + throw new InvalidRequestException("Unknown sort parameter '" + theSort.getParamName() + "'"); + } + + String joinAttrName = "myParamsString"; + String sortAttrName = "myValueExact"; + + switch (param.getParamType()) { + case STRING: { + From stringJoin = theFrom.join(joinAttrName, JoinType.LEFT); + Predicate p = theBuilder.equal(stringJoin.get("myParamName"), theSort.getParamName()); + Predicate pn = theBuilder.isNull(stringJoin.get("myParamName")); + thePredicates.add(theBuilder.or(p,pn)); + theOrders.add(theBuilder.asc(stringJoin.get(sortAttrName))); + break; + } + } + + createSort(theBuilder, theFrom, theSort.getChain(), theOrders, thePredicates); + } + + @Override + public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { + return search(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIds(Map theParams) { + SearchParameterMap map = new SearchParameterMap(); + for (Entry nextEntry : theParams.entrySet()) { + map.add(nextEntry.getKey(), (nextEntry.getValue())); + } + return searchForIdsWithAndOr(map); + } + + @Override + public Set searchForIds(String theParameterName, IQueryParameterType theValue) { + return searchForIds(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIdsWithAndOr(SearchParameterMap theParams) { + SearchParameterMap params = theParams; + if (params == null) { + params = new SearchParameterMap(); + } + + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); + + Set pids = new HashSet(); + + for (Entry>> nextParamEntry : params.entrySet()) { + String nextParamName = nextParamEntry.getKey(); + if (nextParamName.equals("_id")) { + if (nextParamEntry.getValue().isEmpty()) { + continue; + } else if (nextParamEntry.getValue().size() > 1) { + throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)"); + } else { + Set joinPids = new HashSet(); + List nextValue = nextParamEntry.getValue().get(0); + if (nextValue == null || nextValue.size() == 0) { + continue; + } else { + for (IQueryParameterType next : nextValue) { + String value = next.getValueAsQueryToken(); + IdDt valueId = new IdDt(value); + try { + long valueLong = translateForcedIdToPid(valueId); + joinPids.add(valueLong); + } catch (ResourceNotFoundException e) { + // This isn't an error, just means no result found + } + } + if (joinPids.isEmpty()) { + continue; + } + } + + pids = addPredicateId(pids, joinPids); + if (pids.isEmpty()) { + return new HashSet(); + } + + if (pids.isEmpty()) { + pids.addAll(joinPids); + } else { + pids.retainAll(joinPids); + } + + } + } else { + + RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); + if (nextParamDef != null) { + switch (nextParamDef.getParamType()) { + case DATE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateDate(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case QUANTITY: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateQuantity(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case REFERENCE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateReference(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case STRING: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateString(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case TOKEN: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateToken(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case NUMBER: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateNumber(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + case COMPOSITE: + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateComposite(nextParamDef, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + break; + } + } + } + } + + return pids; + } + + @SuppressWarnings("unchecked") + @Required + public void setResourceType(Class theTableType) { + myResourceType = (Class) theTableType; + } + + /** + * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to + * share the same value. + */ + public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { + mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; + } + + @Override + public MethodOutcome update(final T theResource, final IdDt theId) { + StopWatch w = new StopWatch(); + + // TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + // ResourceTable savedEntity = template.execute(new TransactionCallback() { + // @Override + // public ResourceTable doInTransaction(TransactionStatus theStatus) { + // final ResourceTable entity = readEntity(theId); + // return updateEntity(theResource, entity,true); + // } + // }); + + final ResourceTable entity = readEntityLatestVersion(theId); + if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { + throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); + } + + ResourceTable savedEntity = updateEntity(theResource, entity, true, false); + + notifyWriteCompleted(); + ourLog.info("Processed update on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); + return toMethodOutcome(savedEntity); + } + + private Set addPredicateComposite(RuntimeSearchParam theParamDef, Set thePids, List theNextAnd) { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceTable.class); + cq.select(from.get("myId").as(Long.class)); + + IQueryParameterType or = theNextAnd.get(0); + if (!(or instanceof CompositeParam)) { + throw new InvalidRequestException("Invalid type for composite param (must be " + CompositeParam.class.getSimpleName() + ": " + or.getClass()); + } + CompositeParam cp = (CompositeParam) or; + + RuntimeSearchParam left = theParamDef.getCompositeOf().get(0); + IQueryParameterType leftValue = cp.getLeftValue(); + Predicate leftPredicate = createCompositeParamPart(builder, from, left, leftValue); + + RuntimeSearchParam right = theParamDef.getCompositeOf().get(1); + IQueryParameterType rightValue = cp.getRightValue(); + Predicate rightPredicate = createCompositeParamPart(builder, from, right, rightValue); + + Predicate type = builder.equal(from.get("myResourceType"), myResourceName); + if (thePids.size() > 0) { + Predicate inPids = (from.get("myResourcePid").in(thePids)); + cq.where(builder.and(type, leftPredicate, rightPredicate, inPids)); + } else { + cq.where(builder.and(type, leftPredicate, rightPredicate)); + } + + TypedQuery q = myEntityManager.createQuery(cq); + return new HashSet(q.getResultList()); + + } + private Set addPredicateDate(String theParamName, Set thePids, List theList) { if (theList == null || theList.isEmpty()) { return thePids; @@ -115,20 +803,8 @@ public class FhirResourceDao extends BaseFhirDao implements List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { IQueryParameterType params = nextOr; - - if (params instanceof DateParam) { - DateParam date = (DateParam) params; - if (!date.isEmpty()) { - DateRangeParam range = new DateRangeParam(date); - addPredicateDateFromRange(builder, from, codePredicates, range); - } - } else if (params instanceof DateRangeParam) { - DateRangeParam range = (DateRangeParam) params; - addPredicateDateFromRange(builder, from, codePredicates, range); - } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); - } - + Predicate p = createPredicateDate(builder, from, params); + codePredicates.add(p); } Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); @@ -146,15 +822,15 @@ public class FhirResourceDao extends BaseFhirDao implements return new HashSet(q.getResultList()); } - private void addPredicateDateFromRange(CriteriaBuilder builder, Root from, List codePredicates, DateRangeParam range) { - Date lowerBound = range.getLowerBoundAsInstant(); - Date upperBound = range.getUpperBoundAsInstant(); + private Predicate addPredicateDateFromRange(CriteriaBuilder theBuilder, From theFrom, DateRangeParam theRange) { + Date lowerBound = theRange.getLowerBoundAsInstant(); + Date upperBound = theRange.getUpperBoundAsInstant(); Predicate lb = null; if (lowerBound != null) { - Predicate gt = builder.greaterThanOrEqualTo(from. get("myValueLow"), lowerBound); - Predicate lt = builder.greaterThanOrEqualTo(from. get("myValueHigh"), lowerBound); - lb = builder.or(gt, lt); + Predicate gt = theBuilder.greaterThanOrEqualTo(theFrom. get("myValueLow"), lowerBound); + Predicate lt = theBuilder.greaterThanOrEqualTo(theFrom. get("myValueHigh"), lowerBound); + lb = theBuilder.or(gt, lt); // Predicate gin = builder.isNull(from.get("myValueLow")); // Predicate lbo = builder.or(gt, gin); @@ -165,9 +841,9 @@ public class FhirResourceDao extends BaseFhirDao implements Predicate ub = null; if (upperBound != null) { - Predicate gt = builder.lessThanOrEqualTo(from. get("myValueLow"), upperBound); - Predicate lt = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); - ub = builder.or(gt, lt); + Predicate gt = theBuilder.lessThanOrEqualTo(theFrom. get("myValueLow"), upperBound); + Predicate lt = theBuilder.lessThanOrEqualTo(theFrom. get("myValueHigh"), upperBound); + ub = theBuilder.or(gt, lt); // Predicate gin = builder.isNull(from.get("myValueLow")); // Predicate lbo = builder.or(gt, gin); @@ -178,15 +854,15 @@ public class FhirResourceDao extends BaseFhirDao implements } if (lb != null && ub != null) { - codePredicates.add(builder.and(lb, ub)); + return (theBuilder.and(lb, ub)); } else if (lb != null) { - codePredicates.add(lb); + return (lb); } else { - codePredicates.add(ub); + return (ub); } } - private Set addPredicateId(String theParamName, Set theExistingPids, Set thePids) { + private Set addPredicateId(Set theExistingPids, Set thePids) { if (thePids == null || thePids.isEmpty()) { return Collections.emptySet(); } @@ -210,8 +886,72 @@ public class FhirResourceDao extends BaseFhirDao implements return found; } - // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { - // } + private Set addPredicateNumber(String theParamName, Set thePids, List theList) { + if (theList == null || theList.isEmpty()) { + return thePids; + } + + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceIndexedSearchParamNumber.class); + cq.select(from.get("myResourcePid").as(Long.class)); + + List codePredicates = new ArrayList(); + for (IQueryParameterType nextOr : theList) { + IQueryParameterType params = nextOr; + + if (params instanceof NumberParam) { + NumberParam param = (NumberParam) params; + + BigDecimal value = param.getValue(); + if (value == null) { + return thePids; + } + + Path fromObj = from.get("myValue"); + if (param.getComparator() == null) { + double mul = value.doubleValue() * 1.01; + double low = value.doubleValue() - mul; + double high = value.doubleValue() + mul; + Predicate lowPred = builder.ge(fromObj.as(Long.class), low); + Predicate highPred = builder.le(fromObj.as(Long.class), high); + codePredicates.add(builder.and(lowPred, highPred)); + } else { + switch (param.getComparator()) { + case GREATERTHAN: + codePredicates.add(builder.greaterThan(fromObj.as(BigDecimal.class), value)); + break; + case GREATERTHAN_OR_EQUALS: + codePredicates.add(builder.ge(fromObj.as(BigDecimal.class), value)); + break; + case LESSTHAN: + codePredicates.add(builder.lessThan(fromObj.as(BigDecimal.class), value)); + break; + case LESSTHAN_OR_EQUALS: + codePredicates.add(builder.le(fromObj.as(BigDecimal.class), value)); + break; + } + } + } else { + throw new IllegalArgumentException("Invalid token type: " + params.getClass()); + } + + } + + Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); + + Predicate type = builder.equal(from.get("myResourceType"), myResourceName); + Predicate name = builder.equal(from.get("myParamName"), theParamName); + if (thePids.size() > 0) { + Predicate inPids = (from.get("myResourcePid").in(thePids)); + cq.where(builder.and(type, name, masterCodePredicate, inPids)); + } else { + cq.where(builder.and(type, name, masterCodePredicate)); + } + + TypedQuery q = myEntityManager.createQuery(cq); + return new HashSet(q.getResultList()); + } private Set addPredicateQuantity(String theParamName, Set thePids, List theList) { if (theList == null || theList.isEmpty()) { @@ -377,7 +1117,7 @@ public class FhirResourceDao extends BaseFhirDao implements continue; } - IQueryParameterType chainValue = toParameterType(param.getParamType(), resourceId); + IQueryParameterType chainValue = toParameterType(param, resourceId); Set pids = dao.searchForIds(ref.getChain(), chainValue); if (pids.isEmpty()) { continue; @@ -424,38 +1164,9 @@ public class FhirResourceDao extends BaseFhirDao implements List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; + IQueryParameterType theParameter = nextOr; - String rawSearchTerm; - if (params instanceof TokenParam) { - TokenParam id = (TokenParam) params; - if (!id.isText()) { - throw new IllegalStateException("Trying to process a text search on a non-text token parameter"); - } - rawSearchTerm = id.getValue(); - } else if (params instanceof StringParam) { - StringParam id = (StringParam) params; - rawSearchTerm = id.getValue(); - } else if (params instanceof IPrimitiveDatatype) { - IPrimitiveDatatype id = (IPrimitiveDatatype) params; - rawSearchTerm = id.getValueAsString(); - } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); - } - - if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" - + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); - } - - String likeExpression = normalizeString(rawSearchTerm); - likeExpression = likeExpression.replace("%", "[%]") + "%"; - - Predicate singleCode = builder.like(from.get("myValueNormalized").as(String.class), likeExpression); - if (params instanceof StringParam && ((StringParam) params).isExact()) { - Predicate exactCode = builder.equal(from.get("myValueExact"), rawSearchTerm); - singleCode = builder.and(singleCode, exactCode); - } + Predicate singleCode = createPredicateString(theParameter, theParamName, builder, from); codePredicates.add(singleCode); } @@ -474,73 +1185,6 @@ public class FhirResourceDao extends BaseFhirDao implements return new HashSet(q.getResultList()); } - private Set addPredicateNumber(String theParamName, Set thePids, List theList) { - if (theList == null || theList.isEmpty()) { - return thePids; - } - - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceIndexedSearchParamNumber.class); - cq.select(from.get("myResourcePid").as(Long.class)); - - List codePredicates = new ArrayList(); - for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; - - if (params instanceof NumberParam) { - NumberParam param = (NumberParam) params; - - BigDecimal value = param.getValue(); - if (value == null) { - return thePids; - } - - Path fromObj = from.get("myValue"); - if (param.getComparator() == null) { - double mul = value.doubleValue() * 1.01; - double low = value.doubleValue() - mul; - double high = value.doubleValue() + mul; - Predicate lowPred = builder.ge(fromObj.as(Long.class), low); - Predicate highPred = builder.le(fromObj.as(Long.class), high); - codePredicates.add(builder.and(lowPred, highPred)); - } else { - switch (param.getComparator()) { - case GREATERTHAN: - codePredicates.add(builder.greaterThan(fromObj.as(BigDecimal.class), value)); - break; - case GREATERTHAN_OR_EQUALS: - codePredicates.add(builder.ge(fromObj.as(BigDecimal.class), value)); - break; - case LESSTHAN: - codePredicates.add(builder.lessThan(fromObj.as(BigDecimal.class), value)); - break; - case LESSTHAN_OR_EQUALS: - codePredicates.add(builder.le(fromObj.as(BigDecimal.class), value)); - break; - } - } - } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); - } - - } - - Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); - - Predicate type = builder.equal(from.get("myResourceType"), myResourceName); - Predicate name = builder.equal(from.get("myParamName"), theParamName); - if (thePids.size() > 0) { - Predicate inPids = (from.get("myResourcePid").in(thePids)); - cq.where(builder.and(type, name, masterCodePredicate, inPids)); - } else { - cq.where(builder.and(type, name, masterCodePredicate)); - } - - TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); - } - private Set addPredicateToken(String theParamName, Set thePids, List theList) { if (theList == null || theList.isEmpty()) { return thePids; @@ -553,53 +1197,14 @@ public class FhirResourceDao extends BaseFhirDao implements List codePredicates = new ArrayList(); for (IQueryParameterType nextOr : theList) { - IQueryParameterType params = nextOr; - - String code; - String system; - if (params instanceof TokenParam) { - TokenParam id = (TokenParam) params; + if (nextOr instanceof TokenParam) { + TokenParam id = (TokenParam) nextOr; if (id.isText()) { return addPredicateString(theParamName, thePids, theList); } - system = id.getSystem(); - code = id.getValue(); - } else if (params instanceof IdentifierDt) { - IdentifierDt id = (IdentifierDt) params; - system = id.getSystem().getValueAsString(); - code = id.getValue().getValue(); - } else if (params instanceof CodingDt) { - CodingDt id = (CodingDt) params; - system = id.getSystem().getValueAsString(); - code = id.getCode().getValue(); - } else { - throw new IllegalArgumentException("Invalid token type: " + params.getClass()); } - if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" - + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); - } - if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH - + "): " + code); - } - - ArrayList singleCodePredicates = (new ArrayList()); - if (StringUtils.isNotBlank(system)) { - singleCodePredicates.add(builder.equal(from.get("mySystem"), system)); - } else if (system == null) { - // don't check the system - } else { - // If the system is "", we only match on null systems - singleCodePredicates.add(builder.isNull(from.get("mySystem"))); - } - if (StringUtils.isNotBlank(code)) { - singleCodePredicates.add(builder.equal(from.get("myValue"), code)); - } else { - singleCodePredicates.add(builder.isNull(from.get("myValue"))); - } - Predicate singleCode = builder.and(singleCodePredicates.toArray(new Predicate[0])); + Predicate singleCode = createPredicateToken(nextOr, theParamName, builder, from); codePredicates.add(singleCode); } @@ -618,201 +1223,130 @@ public class FhirResourceDao extends BaseFhirDao implements return new HashSet(q.getResultList()); } - @Override - public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); + private Predicate createCompositeParamPart(CriteriaBuilder builder, Root from, RuntimeSearchParam left, IQueryParameterType leftValue) { + Predicate retVal = null; + switch (left.getParamType()) { + case STRING: { + From stringJoin = from.join("myParamsString", JoinType.INNER); + retVal = createPredicateString(leftValue, left.getName(), builder, stringJoin); + break; + } + case TOKEN: { + From tokenJoin = from.join("myParamsToken", JoinType.INNER); + retVal = createPredicateToken(leftValue, left.getName(), builder, tokenJoin); + break; + } + case DATE: { + From dateJoin = from.join("myParamsDate", JoinType.INNER); + retVal = createPredicateDate(builder, dateJoin, leftValue); + break; + } } - for (BaseTag next : new ArrayList(entity.getTags())) { - if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { - return; - } + if (retVal == null) { + throw new InvalidRequestException("Don't know how to handle composite parameter with type of " + left.getParamType()); } - entity.setHasTags(true); - - TagDefinition def = getTag(theScheme, theTerm, theLabel); - BaseTag newEntity = entity.addTag(def); - - myEntityManager.persist(newEntity); - myEntityManager.merge(entity); - notifyWriteCompleted(); - ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); - } - - @Override - public MethodOutcome create(final T theResource) { - StopWatch w = new StopWatch(); - ResourceTable entity = new ResourceTable(); - entity.setResourceType(toResourceName(theResource)); - - if (theResource.getId().isEmpty() == false) { - if (isValidPid(theResource.getId())) { - throw new UnprocessableEntityException( - "This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); - } - createForcedIdIfNeeded(entity, theResource.getId()); - - if (entity.getForcedId() != null) { - try { - translateForcedIdToPid(theResource.getId()); - throw new UnprocessableEntityException("Can not create entity with ID[" + theResource.getId().getValue() + "], constraint violation occurred"); - } catch (ResourceNotFoundException e) { - // good, this ID doesn't exist so we can create it - } - } - - } - - updateEntity(theResource, entity, false, false); - - MethodOutcome outcome = toMethodOutcome(entity); - notifyWriteCompleted(); - ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return outcome; - } - - @Override - public MethodOutcome delete(IdDt theId) { - StopWatch w = new StopWatch(); - final ResourceTable entity = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { - throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); - } - - ResourceTable savedEntity = updateEntity(null, entity, true, true); - - notifyWriteCompleted(); - - ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return toMethodOutcome(savedEntity); - } - - @Override - public TagList getAllResourceTags() { - StopWatch w = new StopWatch(); - TagList tags = super.getTags(myResourceType, null); - ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return tags; - } - - public Class getResourceType() { - return myResourceType; - } - - @Override - public TagList getTags(IdDt theResourceId) { - StopWatch w = new StopWatch(); - TagList retVal = super.getTags(myResourceType, theResourceId); - ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); return retVal; } - @Override - public IBundleProvider history(Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, null, theSince); - ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return retVal; - } + - @Override - public IBundleProvider history(final IdDt theId, final Date theSince) { - final InstantDt end = createHistoryToTimestamp(); - final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); - - T currentTmp; - try { - currentTmp = read(theId.toVersionless()); - if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { - currentTmp = null; + private Predicate createPredicateDate(CriteriaBuilder theBuilder, From theFrom, IQueryParameterType theParam) { + Predicate p; + if (theParam instanceof DateParam) { + DateParam date = (DateParam) theParam; + if (!date.isEmpty()) { + DateRangeParam range = new DateRangeParam(date); + p = addPredicateDateFromRange(theBuilder, theFrom, range); + } else { + // TODO: handle missing date param? + p = null; } - } catch (ResourceNotFoundException e) { - currentTmp = null; - } - - final T current = currentTmp; - - String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" - + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); - TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); - countQuery.setParameter("PID", theId.getIdPartAsLong()); - countQuery.setParameter("RESTYPE", resourceType); - countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - int historyCount = countQuery.getSingleResult().intValue(); - - final int offset; - final int count; - if (current != null) { - count = historyCount + 1; - offset = 1; + } else if (theParam instanceof DateRangeParam) { + DateRangeParam range = (DateRangeParam) theParam; + p = addPredicateDateFromRange(theBuilder, theFrom, range); } else { - offset = 0; - count = historyCount; + throw new IllegalArgumentException("Invalid token type: " + theParam.getClass()); } - - if (count == 0) { - throw new ResourceNotFoundException(theId); - } - - return new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return end; - } - - @Override - public List getResources(int theFromIndex, int theToIndex) { - ArrayList retVal = new ArrayList(); - if (theFromIndex == 0 && current != null) { - retVal.add(current); - } - - TypedQuery q = myEntityManager.createQuery( - "SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " - + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); - q.setParameter("PID", theId.getIdPartAsLong()); - q.setParameter("RESTYPE", resourceType); - q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - - q.setFirstResult(Math.max(0, theFromIndex - offset)); - q.setMaxResults(theToIndex - theFromIndex); - - List results = q.getResultList(); - for (ResourceHistoryTable next : results) { - if (retVal.size() == (theToIndex - theFromIndex)) { - break; - } - retVal.add(toResource(myResourceType, next)); - } - - return retVal; - } - - @Override - public int size() { - return count; - } - }; - + return p; } - @Override - public IBundleProvider history(Long theId, Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, theId, theSince); - ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); - return retVal; + private Predicate createPredicateString(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { + String rawSearchTerm; + if (theParameter instanceof TokenParam) { + TokenParam id = (TokenParam) theParameter; + if (!id.isText()) { + throw new IllegalStateException("Trying to process a text search on a non-text token parameter"); + } + rawSearchTerm = id.getValue(); + } else if (theParameter instanceof StringParam) { + StringParam id = (StringParam) theParameter; + rawSearchTerm = id.getValue(); + } else if (theParameter instanceof IPrimitiveDatatype) { + IPrimitiveDatatype id = (IPrimitiveDatatype) theParameter; + rawSearchTerm = id.getValueAsString(); + } else { + throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass()); + } + + if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { + throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); + } + + String likeExpression = normalizeString(rawSearchTerm); + likeExpression = likeExpression.replace("%", "[%]") + "%"; + + Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); + if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) { + Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm); + singleCode = theBuilder.and(singleCode, exactCode); + } + return singleCode; + } + + private Predicate createPredicateToken(IQueryParameterType theParameter, String theParamName, CriteriaBuilder theBuilder, From theFrom) { + String code; + String system; + if (theParameter instanceof TokenParam) { + TokenParam id = (TokenParam) theParameter; + system = id.getSystem(); + code = id.getValue(); + } else if (theParameter instanceof IdentifierDt) { + IdentifierDt id = (IdentifierDt) theParameter; + system = id.getSystem().getValueAsString(); + code = id.getValue().getValue(); + } else if (theParameter instanceof CodingDt) { + CodingDt id = (CodingDt) theParameter; + system = id.getSystem().getValueAsString(); + code = id.getCode().getValue(); + } else { + throw new IllegalArgumentException("Invalid token type: " + theParameter.getClass()); + } + + if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { + throw new InvalidRequestException("Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); + } + if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { + throw new InvalidRequestException("Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); + } + + ArrayList singleCodePredicates = (new ArrayList()); + if (StringUtils.isNotBlank(system)) { + singleCodePredicates.add(theBuilder.equal(theFrom.get("mySystem"), system)); + } else if (system == null) { + // don't check the system + } else { + // If the system is "", we only match on null systems + singleCodePredicates.add(theBuilder.isNull(theFrom.get("mySystem"))); + } + if (StringUtils.isNotBlank(code)) { + singleCodePredicates.add(theBuilder.equal(theFrom.get("myValue"), code)); + } else { + singleCodePredicates.add(theBuilder.isNull(theFrom.get("myValue"))); + } + Predicate singleCode = theBuilder.and(singleCodePredicates.toArray(new Predicate[0])); + return singleCode; } private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate) { @@ -820,6 +1354,12 @@ public class FhirResourceDao extends BaseFhirDao implements return; } + Map position = new HashMap(); + for (Long next : theIncludePids) { + position.put(next, theResourceListToPopulate.size()); + theResourceListToPopulate.add(null); + } + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery cq = builder.createQuery(ResourceTable.class); Root from = cq.from(ResourceTable.class); @@ -831,89 +1371,13 @@ public class FhirResourceDao extends BaseFhirDao implements for (ResourceTable next : q.getResultList()) { T resource = toResource(myResourceType, next); - theResourceListToPopulate.add(resource); - } - } - - @PostConstruct - public void postConstruct() { - RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); - myResourceName = def.getName(); - - if (mySecondaryPrimaryKeyParamName != null) { - RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); - if (sp == null) { - throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); - } - if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { - throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName - + "] is not a token type, only token is supported"); + Integer index = position.get(next.getId()); + if (index == null) { + ourLog.warn("Got back unexpected resource PID {}", next.getId()); + continue; } + theResourceListToPopulate.set(index, resource); } - - } - - @Override - public T read(IdDt theId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - validateResourceType(entity); - - T retVal = toResource(myResourceType, entity); - - InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); - if (deleted != null && !deleted.isEmpty()) { - throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); - } - - ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return retVal; - } - - private void validateResourceType(BaseHasResource entity) { - if (!myResourceName.equals(entity.getResourceType())) { - throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " - + entity.getResourceType()); - } - } - - private void validateResourceTypeAndThrowIllegalArgumentException(IdDt theId) { - if (theId.hasResourceType() && !theId.getResourceType().equals(myResourceName)) { - throw new IllegalArgumentException("Incorrect resource type (" + theId.getResourceType() + ") for this DAO, wanted: " + myResourceName); - } - } - - @Override - public BaseHasResource readEntity(IdDt theId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - Long pid = translateForcedIdToPid(theId); - BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); - if (theId.hasVersionIdPart()) { - if (entity.getVersion() != theId.getVersionIdPartAsLong()) { - entity = null; - } - } - - if (entity == null) { - if (theId.hasVersionIdPart()) { - TypedQuery q = myEntityManager.createQuery( - "SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); - q.setParameter("RID", theId.getIdPartAsLong()); - q.setParameter("RTYP", myResourceName); - q.setParameter("RVER", theId.getVersionIdPartAsLong()); - entity = q.getSingleResult(); - } - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - } - - validateResourceType(entity); - - return entity; } private ResourceTable readEntityLatestVersion(IdDt theId) { @@ -924,360 +1388,63 @@ public class FhirResourceDao extends BaseFhirDao implements return entity; } - @Override - public void removeTag(IdDt theId, String theScheme, String theTerm) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - - for (BaseTag next : new ArrayList(entity.getTags())) { - if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { - myEntityManager.remove(next); - entity.getTags().remove(next); - } - } - - if (entity.getTags().isEmpty()) { - entity.setHasTags(false); - } - - myEntityManager.merge(entity); - - ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); - } - - @Override - public IBundleProvider search(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.add(nextEntry.getKey(), (nextEntry.getValue())); - } - return search(map); - } - - @Override - public IBundleProvider search(final SearchParameterMap theParams) { - StopWatch w = new StopWatch(); - final InstantDt now = InstantDt.withCurrentTime(); - - Set loadPids; - if (theParams.isEmpty()) { - loadPids = new HashSet(); - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(ResourceTable.class); - cq.multiselect(from.get("myId").as(Long.class)); - cq.where(builder.equal(from.get("myResourceType"), myResourceName)); - - TypedQuery query = myEntityManager.createQuery(cq); - for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); - } - } else { - loadPids = searchForIdsWithAndOr(theParams); - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); - } - } - - final ArrayList pids = new ArrayList(loadPids); - - IBundleProvider retVal = new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return now; - } - - @Override - public List getResources(final int theFromIndex, final int theToIndex) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - return template.execute(new TransactionCallback>() { - @Override - public List doInTransaction(TransactionStatus theStatus) { - List pidsSubList = pids.subList(theFromIndex, theToIndex); - - // Execute the query and make sure we return distinct results - List retVal = new ArrayList(); - loadResourcesByPid(pidsSubList, retVal); - - // Load _include resources - if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { - Set previouslyLoadedPids = new HashSet(); - - Set includePids = new HashSet(); - List resources = retVal; - do { - includePids.clear(); - - FhirTerser t = getContext().newTerser(); - for (Include next : theParams.getIncludes()) { - for (IResource nextResource : resources) { - RuntimeResourceDefinition def = getContext().getResourceDefinition(nextResource); - if (!next.getValue().startsWith(def.getName() + ".")) { - continue; - } - - List values = t.getValues(nextResource, next.getValue()); - for (Object object : values) { - if (object == null) { - continue; - } - if (!(object instanceof ResourceReferenceDt)) { - throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); - } - ResourceReferenceDt rr = (ResourceReferenceDt) object; - if (rr.getReference().isEmpty()) { - continue; - } - if (rr.getReference().isLocal()) { - continue; - } - - IdDt nextId = rr.getReference().toUnqualified(); - if (!previouslyLoadedPids.contains(nextId)) { - includePids.add(nextId); - previouslyLoadedPids.add(nextId); - } - } - } - } - - if (!includePids.isEmpty()) { - ourLog.info("Loading {} included resources", includePids.size()); - resources = loadResourcesById(includePids); - retVal.addAll(resources); - } - } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); - - if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); - retVal.add(0, oo); - } - } - - - return retVal; - } - }); - } - - @Override - public int size() { - return pids.size(); - } - }; - - ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); - - return retVal; - } - - @Override - public IBundleProvider search(String theParameterName, IQueryParameterType theValue) { - return search(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public Set searchForIds(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.add(nextEntry.getKey(), (nextEntry.getValue())); - } - return searchForIdsWithAndOr(map); - } - - @Override - public Set searchForIds(String theParameterName, IQueryParameterType theValue) { - return searchForIds(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public Set searchForIdsWithAndOr(SearchParameterMap theParams) { - SearchParameterMap params = theParams; - if (params == null) { - params = new SearchParameterMap(); - } - - RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType); - - Set pids = new HashSet(); - - for (Entry>> nextParamEntry : params.entrySet()) { - String nextParamName = nextParamEntry.getKey(); - if (nextParamName.equals("_id")) { - if (nextParamEntry.getValue().isEmpty()) { - continue; - } else if (nextParamEntry.getValue().size() > 1) { - throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)"); - } else { - Set joinPids = new HashSet(); - List nextValue = nextParamEntry.getValue().get(0); - if (nextValue == null || nextValue.size() == 0) { - continue; - } else { - for (IQueryParameterType next : nextValue) { - String value = next.getValueAsQueryToken(); - IdDt valueId = new IdDt(value); - try { - long valueLong = translateForcedIdToPid(valueId); - joinPids.add(valueLong); - } catch (ResourceNotFoundException e) { - // This isn't an error, just means no result found - } - } - if (joinPids.isEmpty()) { - continue; - } - } - - pids = addPredicateId(nextParamName, pids, joinPids); - if (pids.isEmpty()) { - return new HashSet(); - } - - if (pids.isEmpty()) { - pids.addAll(joinPids); - } else { - pids.retainAll(joinPids); - } - - } - } else { - - RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); - if (nextParamDef != null) { - switch (nextParamDef.getParamType()) { - case DATE: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateDate(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case QUANTITY: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateQuantity(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case REFERENCE: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateReference(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case STRING: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateString(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case TOKEN: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateToken(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case NUMBER: - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateNumber(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - break; - case COMPOSITE: - throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType()); - } - } - } - } - - return pids; - } - - @SuppressWarnings("unchecked") - @Required - public void setResourceType(Class theTableType) { - myResourceType = (Class) theTableType; - } - - /** - * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value. - */ - public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { - mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; - } - private MethodOutcome toMethodOutcome(final ResourceTable entity) { MethodOutcome outcome = new MethodOutcome(); outcome.setId(entity.getIdDt()); return outcome; } - private IQueryParameterType toParameterType(SearchParamTypeEnum theParamType, String theValueAsQueryToken) { - switch (theParamType) { + private IQueryParameterType toParameterType(RuntimeSearchParam theParam) { + IQueryParameterType qp; + switch (theParam.getParamType()) { case DATE: - return new DateParam(theValueAsQueryToken); + qp = new DateParam(); + break; case NUMBER: - QuantityDt qt = new QuantityDt(); - qt.setValueAsQueryToken(null, theValueAsQueryToken); - return qt; + qp = new NumberParam(); + break; case QUANTITY: - qt = new QuantityDt(); - qt.setValueAsQueryToken(null, theValueAsQueryToken); - return qt; + qp = new QuantityParam(); + break; case STRING: - StringDt st = new StringDt(); - st.setValueAsQueryToken(null, theValueAsQueryToken); - return st; + qp = new StringParam(); + break; case TOKEN: - IdentifierDt id = new IdentifierDt(); - id.setValueAsQueryToken(null, theValueAsQueryToken); - return id; + qp = new TokenParam(); + break; case COMPOSITE: + List compositeOf = theParam.getCompositeOf(); + if (compositeOf.size() != 2) { + throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this."); + } + IQueryParameterType leftParam = toParameterType(compositeOf.get(0)); + IQueryParameterType rightParam = toParameterType(compositeOf.get(1)); + qp = new CompositeParam(leftParam, rightParam); + break; case REFERENCE: default: - throw new IllegalStateException("Don't know how to convert param type: " + theParamType); + throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType()); + } + return qp; + } + + private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theValueAsQueryToken) { + IQueryParameterType qp = toParameterType(theParam); + + qp.setValueAsQueryToken(null, theValueAsQueryToken); + return qp; + } + + private void validateResourceType(BaseHasResource entity) { + if (!myResourceName.equals(entity.getResourceType())) { + throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType()); } } - @Override - public MethodOutcome update(final T theResource, final IdDt theId) { - StopWatch w = new StopWatch(); - - // TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - // ResourceTable savedEntity = template.execute(new TransactionCallback() { - // @Override - // public ResourceTable doInTransaction(TransactionStatus theStatus) { - // final ResourceTable entity = readEntity(theId); - // return updateEntity(theResource, entity,true); - // } - // }); - - final ResourceTable entity = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { - throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); + private void validateResourceTypeAndThrowIllegalArgumentException(IdDt theId) { + if (theId.hasResourceType() && !theId.getResourceType().equals(myResourceName)) { + throw new IllegalArgumentException("Incorrect resource type (" + theId.getResourceType() + ") for this DAO, wanted: " + myResourceName); } - - ResourceTable savedEntity = updateEntity(theResource, entity, true, false); - - notifyWriteCompleted(); - ourLog.info("Processed update on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return toMethodOutcome(savedEntity); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java index ed5c6cccf03..4206516cfb1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java @@ -13,6 +13,7 @@ import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.rest.api.SortSpec; public class SearchParameterMap extends HashMap>> { @@ -20,6 +21,8 @@ public class SearchParameterMap extends HashMap myIncludes; + private SortSpec mySort; + public void add(String theName, IQueryParameterAnd theAnd) { if (theAnd == null) { return; @@ -59,6 +62,10 @@ public class SearchParameterMap extends HashMap getIncludes() { if (myIncludes == null) { myIncludes = new HashSet(); @@ -66,12 +73,16 @@ public class SearchParameterMap extends HashMap theIncludes) { myIncludes = theIncludes; } - public void addInclude(Include theInclude) { - getIncludes().add(theInclude); + public void setSort(SortSpec theSort) { + mySort = theSort; } @Override diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java index 8a131285ef3..f38c9282f65 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoTest.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -41,13 +42,15 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.IdentifierListParam; import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -57,15 +60,15 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; public class FhirResourceDaoTest { private static ClassPathXmlApplicationContext ourCtx; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoTest.class); - private static IFhirResourceDao ourObservationDao; - private static IFhirResourceDao ourPatientDao; private static IFhirResourceDao ourDeviceDao; private static IFhirResourceDao ourDiagnosticReportDao; - private static IFhirResourceDao ourOrganizationDao; - private static IFhirResourceDao ourLocationDao; private static IFhirResourceDao ourEncounterDao; private static FhirContext ourFhirCtx; + private static IFhirResourceDao ourLocationDao; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoTest.class); + private static IFhirResourceDao ourObservationDao; + private static IFhirResourceDao ourOrganizationDao; + private static IFhirResourceDao ourPatientDao; @Test public void testChoiceParamConcept() { @@ -139,211 +142,218 @@ public class FhirResourceDaoTest { } @Test - public void testOrganizationName() { - - //@formatter:off - String inputStr = "{\"resourceType\":\"Organization\",\n" + - " \"extension\":[\n" + - " {\n" + - " \"url\":\"http://fhir.connectinggta.ca/Profile/organization#providerIdPool\",\n" + - " \"valueUri\":\"urn:oid:2.16.840.1.113883.3.239.23.21.1\"\n" + - " }\n" + - " ],\n" + - " \"text\":{\n" + - " \"status\":\"empty\",\n" + - " \"div\":\"
No narrative template available for resource profile: http://fhir.connectinggta.ca/Profile/organization
\"\n" + - " },\n" + - " \"identifier\":[\n" + - " {\n" + - " \"use\":\"official\",\n" + - " \"label\":\"HSP 2.16.840.1.113883.3.239.23.21\",\n" + - " \"system\":\"urn:cgta:hsp_ids\",\n" + - " \"value\":\"urn:oid:2.16.840.1.113883.3.239.23.21\"\n" + - " }\n" + - " ],\n" + - " \"name\":\"Peterborough Regional Health Centre\"\n" + - " }\n" + - " }"; - //@formatter:on - - Set val = ourOrganizationDao.searchForIds("name", new StringParam("P")); - int initial = val.size(); - - Organization org = ourFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); - ourOrganizationDao.create(org); - - val = ourOrganizationDao.searchForIds("name", new StringParam("P")); - assertEquals(initial + 1, val.size()); - - } - - @Test - public void testStringParamWhichIsTooLong() { - - Organization org = new Organization(); - String str = "testStringParamLong__lvdaoy843s89tll8gvs89l4s3gelrukveilufyebrew8r87bv4b77feli7fsl4lv3vb7rexloxe7olb48vov4o78ls7bvo7vb48o48l4bb7vbvx"; - str = str + str; - org.getName().setValue(str); - - assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); - - Set val = ourOrganizationDao.searchForIds("name", new StringParam("P")); - int initial = val.size(); - - ourOrganizationDao.create(org); - - val = ourOrganizationDao.searchForIds("name", new StringParam("P")); - assertEquals(initial + 0, val.size()); - - val = ourOrganizationDao.searchForIds("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH))); - assertEquals(initial + 1, val.size()); - - try { - ourOrganizationDao.searchForIds("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH + 1))); - fail(); - } catch (InvalidRequestException e) { - // ok - } - } - - @Test - public void testTokenParamWhichIsTooLong() { - - String longStr1 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); - String longStr2 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); - - Organization org = new Organization(); - org.getName().setValue("testTokenParamWhichIsTooLong"); - org.getType().addCoding().setSystem(longStr1).setCode(longStr2); - - String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); - String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); - Set val = ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, subStr2)); - int initial = val.size(); - - ourOrganizationDao.create(org); - - val = ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, subStr2)); - assertEquals(initial + 1, val.size()); - - try { - ourOrganizationDao.searchForIds("type", new IdentifierDt(longStr1, subStr2)); - fail(); - } catch (InvalidRequestException e) { - // ok - } - - try { - ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, longStr2)); - fail(); - } catch (InvalidRequestException e) { - // ok - } - } - - @Test - public void testStoreUnversionedResources() { - Organization o1 = new Organization(); - o1.getName().setValue("AAA"); - IdDt o1id = ourOrganizationDao.create(o1).getId(); - assertTrue(o1id.hasVersionIdPart()); - - Patient p1 = new Patient(); - p1.addName().addFamily("AAAA"); - p1.getManagingOrganization().setReference(o1id); - IdDt p1id = ourPatientDao.create(p1).getId(); - - p1 = ourPatientDao.read(p1id); - - assertFalse(p1.getManagingOrganization().getReference().hasVersionIdPart()); - assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless()); - } - - @Test - public void testSearchForUnknownAlphanumericId() { - { - SearchParameterMap map = new SearchParameterMap(); - map.add("_id", new StringParam("testSearchForUnknownAlphanumericId")); - IBundleProvider retrieved = ourPatientDao.search(map); - assertEquals(0, retrieved.size()); - } - } - - @Test - public void testSearchNumberParam() { - Encounter e1 = new Encounter(); - e1.addIdentifier("foo", "testSearchNumberParam01"); - e1.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("min").setValue(4.0 * 24 * 60); - IdDt id1 = ourEncounterDao.create(e1).getId(); - - Encounter e2 = new Encounter(); - e2.addIdentifier("foo", "testSearchNumberParam02"); - e2.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("year").setValue(2.0); - IdDt id2 = ourEncounterDao.create(e2).getId(); - - IBundleProvider found = ourEncounterDao.search(Encounter.SP_LENGTH, new NumberParam(">2")); - assertEquals(2, found.size()); - - } - - @Test - public void testSearchTokenParam() { + public void testCreateWithInvalidReferenceFailsGracefully() { Patient patient = new Patient(); - patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001"); - patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1"); - patient.addCommunication().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem").setDisplay("testSearchTokenParamDisplay"); - ourPatientDao.create(patient); + patient.addName().addFamily("testSearchResourceLinkWithChainWithMultipleTypes01"); + patient.setManagingOrganization(new ResourceReferenceDt("Patient/99999999")); + try { + ourPatientDao.create(patient); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), StringContains.containsString("99999 not found")); + } - patient = new Patient(); - patient.addIdentifier("urn:system", "testSearchTokenParam002"); - patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2"); - ourPatientDao.create(patient); + } + @Test + public void testDatePeriodParamEndOnly() { { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); - IBundleProvider retrieved = ourPatientDao.search(map); - assertEquals(1, retrieved.size()); + Encounter enc = new Encounter(); + enc.addIdentifier("testDatePeriodParam", "02"); + enc.getPeriod().getEnd().setValueAsString("2001-01-02"); + ourEncounterDao.create(enc); + } + SearchParameterMap params; + List encs; + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); + // encs = toList(ourEncounterDao.search(params)); + // assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDatePeriodParamStartAndEnd() { + { + Encounter enc = new Encounter(); + enc.addIdentifier("testDatePeriodParam", "03"); + enc.getPeriod().getStart().setValueAsString("2001-01-02"); + enc.getPeriod().getEnd().setValueAsString("2001-01-03"); + ourEncounterDao.create(enc); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + List encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDatePeriodParamStartOnly() { + { + Encounter enc = new Encounter(); + enc.addIdentifier("testDatePeriodParam", "01"); + enc.getPeriod().getStart().setValueAsString("2001-01-02"); + ourEncounterDao.create(enc); + } + + SearchParameterMap params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); + List encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(1, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + params = new SearchParameterMap(); + params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); + params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); + encs = toList(ourEncounterDao.search(params)); + assertEquals(0, encs.size()); + + } + + @Test + public void testDelete() { + int initialHistory = ourPatientDao.history(null).size(); + + IdDt id1; + IdDt id2; + IdDt id2b; + { + Patient patient = new Patient(); + patient.addIdentifier("urn:system", "001"); + patient.addName().addFamily("Tester_testDelete").addGiven("Joe"); + id1 = ourPatientDao.create(patient).getId(); } { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_IDENTIFIER, new IdentifierDt(null, "testSearchTokenParam001")); - IBundleProvider retrieved = ourPatientDao.search(map); - assertEquals(1, retrieved.size()); + Patient patient = new Patient(); + patient.addIdentifier("urn:system", "002"); + patient.addName().addFamily("Tester_testDelete").addGiven("John"); + id2 = ourPatientDao.create(patient).getId(); } { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_LANGUAGE, new IdentifierDt("testSearchTokenParamSystem", "testSearchTokenParamCode")); - assertEquals(1, ourPatientDao.search(map).size()); + Patient patient = ourPatientDao.read(id2); + patient.addIdentifier("ZZZZZZZ", "ZZZZZZZZZ"); + id2b = ourPatientDao.update(patient, id2).getId(); } - { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); - assertEquals(0, ourPatientDao.search(map).size()); + ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b }); + + Map params = new HashMap(); + params.put(Patient.SP_FAMILY, new StringDt("Tester_testDelete")); + List patients = toList(ourPatientDao.search(params)); + assertEquals(2, patients.size()); + + ourPatientDao.delete(id1); + + patients = toList(ourPatientDao.search(params)); + assertEquals(1, patients.size()); + + ourPatientDao.read(id1); + try { + ourPatientDao.read(id1.toVersionless()); + fail(); + } catch (ResourceGoneException e) { + // good } - { - SearchParameterMap map = new SearchParameterMap(); - map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamComText", true)); - assertEquals(1, ourPatientDao.search(map).size()); - } - { - SearchParameterMap map = new SearchParameterMap(); - IdentifierListParam listParam = new IdentifierListParam(); - listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam001")); - listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002")); - map.add(Patient.SP_IDENTIFIER, listParam); - IBundleProvider retrieved = ourPatientDao.search(map); - assertEquals(2, retrieved.size()); - } - { - SearchParameterMap map = new SearchParameterMap(); - IdentifierListParam listParam = new IdentifierListParam(); - listParam.addIdentifier(new IdentifierDt(null, "testSearchTokenParam001")); - listParam.addIdentifier(new IdentifierDt("urn:system", "testSearchTokenParam002")); - map.add(Patient.SP_IDENTIFIER, listParam); - IBundleProvider retrieved = ourPatientDao.search(map); - assertEquals(2, retrieved.size()); + + IBundleProvider history = ourPatientDao.history(null); + assertEquals(4 + initialHistory, history.size()); + List resources = history.getResources(0, 4); + assertNotNull(resources.get(0).getResourceMetadata().get(ResourceMetadataKeyEnum.DELETED_AT)); + + try { + ourPatientDao.delete(id2); + fail(); + } catch (InvalidRequestException e) { + // good } + + ourPatientDao.delete(id2.toVersionless()); + + patients = toList(ourPatientDao.search(params)); + assertEquals(0, patients.size()); + } @Test @@ -418,6 +428,45 @@ public class FhirResourceDaoTest { } } + @Test + public void testOrganizationName() { + + //@formatter:off + String inputStr = "{\"resourceType\":\"Organization\",\n" + + " \"extension\":[\n" + + " {\n" + + " \"url\":\"http://fhir.connectinggta.ca/Profile/organization#providerIdPool\",\n" + + " \"valueUri\":\"urn:oid:2.16.840.1.113883.3.239.23.21.1\"\n" + + " }\n" + + " ],\n" + + " \"text\":{\n" + + " \"status\":\"empty\",\n" + + " \"div\":\"
No narrative template available for resource profile: http://fhir.connectinggta.ca/Profile/organization
\"\n" + + " },\n" + + " \"identifier\":[\n" + + " {\n" + + " \"use\":\"official\",\n" + + " \"label\":\"HSP 2.16.840.1.113883.3.239.23.21\",\n" + + " \"system\":\"urn:cgta:hsp_ids\",\n" + + " \"value\":\"urn:oid:2.16.840.1.113883.3.239.23.21\"\n" + + " }\n" + + " ],\n" + + " \"name\":\"Peterborough Regional Health Centre\"\n" + + " }\n" + + " }"; + //@formatter:on + + Set val = ourOrganizationDao.searchForIds("name", new StringParam("P")); + int initial = val.size(); + + Organization org = ourFhirCtx.newJsonParser().parseResource(Organization.class, inputStr); + ourOrganizationDao.create(org); + + val = ourOrganizationDao.searchForIds("name", new StringParam("P")); + assertEquals(initial + 1, val.size()); + + } + @Test public void testPersistResourceLink() { Patient patient = new Patient(); @@ -491,21 +540,6 @@ public class FhirResourceDaoTest { } - @Test - public void testSearchWithNoResults() { - IBundleProvider value = ourDeviceDao.search(new SearchParameterMap()); - for (IResource next : value.getResources(0, value.size())) { - ourDeviceDao.delete(next.getId()); - } - - value = ourDeviceDao.search(new SearchParameterMap()); - assertEquals(0, value.size()); - - List res = value.getResources(0, 0); - assertTrue(res.isEmpty()); - - } - @Test public void testPersistSearchParamQuantity() { Observation obs = new Observation(); @@ -585,6 +619,105 @@ public class FhirResourceDaoTest { assertTrue(patients.size() >= 2); } + @Test + public void testSearchByIdParam() { + IdDt id1; + { + Patient patient = new Patient(); + patient.addIdentifier("urn:system", "001"); + id1 = ourPatientDao.create(patient).getId(); + } + IdDt id2; + { + Organization patient = new Organization(); + patient.addIdentifier("urn:system", "001"); + id2 = ourOrganizationDao.create(patient).getId(); + } + + Map params = new HashMap(); + params.put("_id", new StringDt(id1.getIdPart())); + assertEquals(1, toList(ourPatientDao.search(params)).size()); + + params.put("_id", new StringDt("9999999999999999")); + assertEquals(0, toList(ourPatientDao.search(params)).size()); + + params.put("_id", new StringDt(id2.getIdPart())); + assertEquals(0, toList(ourPatientDao.search(params)).size()); + + } + + @Test + public void testSearchCompositeParam() { + Observation o1 = new Observation(); + o1.getName().addCoding().setSystem("foo").setCode("testSearchCompositeParamN01"); + o1.setValue(new StringDt("testSearchCompositeParamS01")); + IdDt id1 = ourObservationDao.create(o1).getId(); + + Observation o2 = new Observation(); + o2.getName().addCoding().setSystem("foo").setCode("testSearchCompositeParamN01"); + o2.setValue(new StringDt("testSearchCompositeParamS02")); + IdDt id2 = ourObservationDao.create(o2).getId(); + + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01"); + StringParam v1 = new StringParam("testSearchCompositeParamS01"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = ourObservationDao.search(Observation.SP_NAME_VALUE_STRING, val); + assertEquals(1, result.size()); + assertEquals(id1.toUnqualifiedVersionless(), result.getResources(0, 1).get(0).getId().toUnqualifiedVersionless()); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01"); + StringParam v1 = new StringParam("testSearchCompositeParamS02"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = ourObservationDao.search(Observation.SP_NAME_VALUE_STRING, val); + assertEquals(1, result.size()); + assertEquals(id2.toUnqualifiedVersionless(), result.getResources(0, 1).get(0).getId().toUnqualifiedVersionless()); + } + } + + @Test + public void testSearchCompositeParamDate() { + Observation o1 = new Observation(); + o1.getName().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); + o1.setValue(new PeriodDt().setStart(new DateTimeDt("2001-01-01T11:11:11"))); + IdDt id1 = ourObservationDao.create(o1).getId().toUnqualifiedVersionless(); + + Observation o2 = new Observation(); + o2.getName().addCoding().setSystem("foo").setCode("testSearchCompositeParamDateN01"); + o2.setValue(new PeriodDt().setStart(new DateTimeDt("2001-01-01T12:12:12"))); + IdDt id2 = ourObservationDao.create(o2).getId().toUnqualifiedVersionless(); + + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + DateParam v1 = new DateParam("2001-01-01T11:11:11"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = ourObservationDao.search(Observation.SP_NAME_VALUE_DATE, val); + assertEquals(1, result.size()); + assertEquals(id1.toUnqualifiedVersionless(), result.getResources(0, 1).get(0).getId().toUnqualifiedVersionless()); + } + { + TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamDateN01"); + // TODO: this should also work with ">2001-01-01T15:12:12" since the two times only have a lower bound + DateParam v1 = new DateParam(">2001-01-01T10:12:12"); + CompositeParam val = new CompositeParam(v0, v1); + IBundleProvider result = ourObservationDao.search(Observation.SP_NAME_VALUE_DATE, val); + assertEquals(2, result.size()); + assertThat(toUnqualifiedVersionlessIds(result), containsInAnyOrder(id1, id2)); + } + + } + + @Test + public void testSearchForUnknownAlphanumericId() { + { + SearchParameterMap map = new SearchParameterMap(); + map.add("_id", new StringParam("testSearchForUnknownAlphanumericId")); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(0, retrieved.size()); + } + } + @Test public void testSearchNameParam() { IdDt id1; @@ -635,30 +768,30 @@ public class FhirResourceDaoTest { } @Test - public void testSearchByIdParam() { - IdDt id1; + public void testSearchNumberParam() { + Encounter e1 = new Encounter(); + e1.addIdentifier("foo", "testSearchNumberParam01"); + e1.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("min").setValue(4.0 * 24 * 60); + IdDt id1 = ourEncounterDao.create(e1).getId(); + + Encounter e2 = new Encounter(); + e2.addIdentifier("foo", "testSearchNumberParam02"); + e2.getLength().setSystem(BaseFhirDao.UCUM_NS).setCode("year").setValue(2.0); + IdDt id2 = ourEncounterDao.create(e2).getId(); { - Patient patient = new Patient(); - patient.addIdentifier("urn:system", "001"); - id1 = ourPatientDao.create(patient).getId(); + IBundleProvider found = ourEncounterDao.search(Encounter.SP_LENGTH, new NumberParam(">2")); + assertEquals(2, found.size()); + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1.toUnqualifiedVersionless(), id2.toUnqualifiedVersionless())); } - IdDt id2; { - Organization patient = new Organization(); - patient.addIdentifier("urn:system", "001"); - id2 = ourOrganizationDao.create(patient).getId(); + IBundleProvider found = ourEncounterDao.search(Encounter.SP_LENGTH, new NumberParam("<1")); + assertEquals(0, found.size()); + } + { + IBundleProvider found = ourEncounterDao.search(Encounter.SP_LENGTH, new NumberParam("2")); + assertEquals(1, found.size()); + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1.toUnqualifiedVersionless())); } - - Map params = new HashMap(); - params.put("_id", new StringDt(id1.getIdPart())); - assertEquals(1, toList(ourPatientDao.search(params)).size()); - - params.put("_id", new StringDt("9999999999999999")); - assertEquals(0, toList(ourPatientDao.search(params)).size()); - - params.put("_id", new StringDt(id2.getIdPart())); - assertEquals(0, toList(ourPatientDao.search(params)).size()); - } @Test @@ -702,20 +835,6 @@ public class FhirResourceDaoTest { } - @Test - public void testCreateWithInvalidReferenceFailsGracefully() { - Patient patient = new Patient(); - patient.addName().addFamily("testSearchResourceLinkWithChainWithMultipleTypes01"); - patient.setManagingOrganization(new ResourceReferenceDt("Patient/99999999")); - try { - ourPatientDao.create(patient); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), StringContains.containsString("99999 not found")); - } - - } - @Test public void testSearchResourceLinkWithChainWithMultipleTypes() { Patient patient = new Patient(); @@ -781,68 +900,93 @@ public class FhirResourceDaoTest { } @Test - public void testDelete() { - int initialHistory = ourPatientDao.history(null).size(); - - IdDt id1; - IdDt id2; - IdDt id2b; + public void testSearchStringParamWithNonNormalized() { { Patient patient = new Patient(); patient.addIdentifier("urn:system", "001"); - patient.addName().addFamily("Tester_testDelete").addGiven("Joe"); - id1 = ourPatientDao.create(patient).getId(); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_höra"); + ourPatientDao.create(patient); } { Patient patient = new Patient(); patient.addIdentifier("urn:system", "002"); - patient.addName().addFamily("Tester_testDelete").addGiven("John"); - id2 = ourPatientDao.create(patient).getId(); + patient.addName().addGiven("testSearchStringParamWithNonNormalized_HORA"); + ourPatientDao.create(patient); } - { - Patient patient = ourPatientDao.read(id2); - patient.addIdentifier("ZZZZZZZ", "ZZZZZZZZZ"); - id2b = ourPatientDao.update(patient, id2).getId(); - } - ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b }); Map params = new HashMap(); - params.put(Patient.SP_FAMILY, new StringDt("Tester_testDelete")); + params.put(Patient.SP_GIVEN, new StringDt("testSearchStringParamWithNonNormalized_hora")); List patients = toList(ourPatientDao.search(params)); assertEquals(2, patients.size()); - ourPatientDao.delete(id1); - - patients = toList(ourPatientDao.search(params)); - assertEquals(1, patients.size()); - - ourPatientDao.read(id1); - try { - ourPatientDao.read(id1.toVersionless()); - fail(); - } catch (ResourceGoneException e) { - // good - } - - IBundleProvider history = ourPatientDao.history(null); - assertEquals(4 + initialHistory, history.size()); - List resources = history.getResources(0, 4); - assertNotNull(resources.get(0).getResourceMetadata().get(ResourceMetadataKeyEnum.DELETED_AT)); - - try { - ourPatientDao.delete(id2); - fail(); - } catch (InvalidRequestException e) { - // good - } - - ourPatientDao.delete(id2.toVersionless()); - + StringParam parameter = new StringParam("testSearchStringParamWithNonNormalized_hora"); + parameter.setExact(true); + params.put(Patient.SP_GIVEN, parameter); patients = toList(ourPatientDao.search(params)); assertEquals(0, patients.size()); } + @Test + public void testSearchTokenParam() { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001"); + patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1"); + patient.addCommunication().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem").setDisplay("testSearchTokenParamDisplay"); + ourPatientDao.create(patient); + + patient = new Patient(); + patient.addIdentifier("urn:system", "testSearchTokenParam002"); + patient.addName().addFamily("Tester").addGiven("testSearchTokenParam2"); + ourPatientDao.create(patient); + + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testSearchTokenParam001")); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(1, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_IDENTIFIER, new IdentifierDt(null, "testSearchTokenParam001")); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(1, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new IdentifierDt("testSearchTokenParamSystem", "testSearchTokenParamCode")); + assertEquals(1, ourPatientDao.search(map).size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamCode", true)); + assertEquals(0, ourPatientDao.search(map).size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + map.add(Patient.SP_LANGUAGE, new TokenParam(null, "testSearchTokenParamComText", true)); + assertEquals(1, ourPatientDao.search(map).size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + TokenOrListParam listParam = new TokenOrListParam(); + listParam.add(new IdentifierDt("urn:system", "testSearchTokenParam001")); + listParam.add(new IdentifierDt("urn:system", "testSearchTokenParam002")); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(2, retrieved.size()); + } + { + SearchParameterMap map = new SearchParameterMap(); + TokenOrListParam listParam = new TokenOrListParam(); + listParam.add(new IdentifierDt(null, "testSearchTokenParam001")); + listParam.add(new IdentifierDt("urn:system", "testSearchTokenParam002")); + map.add(Patient.SP_IDENTIFIER, listParam); + IBundleProvider retrieved = ourPatientDao.search(map); + assertEquals(2, retrieved.size()); + } + } + @Test public void testSearchWithIncludes() { { @@ -880,173 +1024,95 @@ public class FhirResourceDaoTest { } @Test - public void testDatePeriodParamStartOnly() { - { - Encounter enc = new Encounter(); - enc.addIdentifier("testDatePeriodParam", "01"); - enc.getPeriod().getStart().setValueAsString("2001-01-02"); - ourEncounterDao.create(enc); + public void testSearchWithNoResults() { + IBundleProvider value = ourDeviceDao.search(new SearchParameterMap()); + for (IResource next : value.getResources(0, value.size())) { + ourDeviceDao.delete(next.getId()); } - SearchParameterMap params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); - List encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); + value = ourDeviceDao.search(new SearchParameterMap()); + assertEquals(0, value.size()); - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); + List res = value.getResources(0, 0); + assertTrue(res.isEmpty()); } @Test - public void testDatePeriodParamEndOnly() { - { - Encounter enc = new Encounter(); - enc.addIdentifier("testDatePeriodParam", "02"); - enc.getPeriod().getEnd().setValueAsString("2001-01-02"); - ourEncounterDao.create(enc); - } + public void testSort() { + Patient p = new Patient(); + p.addIdentifier("urn:system", "testSort001"); + p.addName().addFamily("testSortF1").addGiven("testSortG1"); + IdDt id1 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); - SearchParameterMap params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); - List encs; - // encs = toList(ourEncounterDao.search(params)); - // assertEquals(1, encs.size()); + // Create out of order + p = new Patient(); + p.addIdentifier("urn:system", "testSort001"); + p.addName().addFamily("testSortF3").addGiven("testSortG3"); + IdDt id3 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); + p = new Patient(); + p.addIdentifier("urn:system", "testSort001"); + p.addName().addFamily("testSortF2").addGiven("testSortG2"); + IdDt id2 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); + p = new Patient(); + p.addIdentifier("urn:system", "testSort001"); + IdDt id4 = ourPatientDao.create(p).getId().toUnqualifiedVersionless(); - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); - - } - - @SuppressWarnings("unchecked") - private List toList(IBundleProvider theSearch) { - return (List) theSearch.getResources(0, theSearch.size()); + SearchParameterMap pm = new SearchParameterMap(); + pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testSort001")); + pm.setSort(new SortSpec(Patient.SP_FAMILY)); + List actual = toUnqualifiedVersionlessIds(ourPatientDao.search(pm)); + assertEquals(4, actual.size()); + assertThat(actual, contains(id1, id2, id3, id4)); } @Test - public void testDatePeriodParamStartAndEnd() { - { - Encounter enc = new Encounter(); - enc.addIdentifier("testDatePeriodParam", "03"); - enc.getPeriod().getStart().setValueAsString("2001-01-02"); - enc.getPeriod().getEnd().setValueAsString("2001-01-03"); - ourEncounterDao.create(enc); - } + public void testStoreUnversionedResources() { + Organization o1 = new Organization(); + o1.getName().setValue("AAA"); + IdDt o1id = ourOrganizationDao.create(o1).getId(); + assertTrue(o1id.hasVersionIdPart()); - SearchParameterMap params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - List encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); + Patient p1 = new Patient(); + p1.addName().addFamily("AAAA"); + p1.getManagingOrganization().setReference(o1id); + IdDt p1id = ourPatientDao.create(p1).getId(); - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-01", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-03")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-05")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(1, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam(null, "2001-01-01")); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); - - params = new SearchParameterMap(); - params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null)); - params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03")); - encs = toList(ourEncounterDao.search(params)); - assertEquals(0, encs.size()); + p1 = ourPatientDao.read(p1id); + assertFalse(p1.getManagingOrganization().getReference().hasVersionIdPart()); + assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless()); } @Test - public void testSearchStringParamWithNonNormalized() { - { - Patient patient = new Patient(); - patient.addIdentifier("urn:system", "001"); - patient.addName().addGiven("testSearchStringParamWithNonNormalized_höra"); - ourPatientDao.create(patient); + public void testStringParamWhichIsTooLong() { + + Organization org = new Organization(); + String str = "testStringParamLong__lvdaoy843s89tll8gvs89l4s3gelrukveilufyebrew8r87bv4b77feli7fsl4lv3vb7rexloxe7olb48vov4o78ls7bvo7vb48o48l4bb7vbvx"; + str = str + str; + org.getName().setValue(str); + + assertThat(str.length(), greaterThan(ResourceIndexedSearchParamString.MAX_LENGTH)); + + Set val = ourOrganizationDao.searchForIds("name", new StringParam("P")); + int initial = val.size(); + + ourOrganizationDao.create(org); + + val = ourOrganizationDao.searchForIds("name", new StringParam("P")); + assertEquals(initial + 0, val.size()); + + val = ourOrganizationDao.searchForIds("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH))); + assertEquals(initial + 1, val.size()); + + try { + ourOrganizationDao.searchForIds("name", new StringParam(str.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH + 1))); + fail(); + } catch (InvalidRequestException e) { + // ok } - { - Patient patient = new Patient(); - patient.addIdentifier("urn:system", "002"); - patient.addName().addGiven("testSearchStringParamWithNonNormalized_HORA"); - ourPatientDao.create(patient); - } - - Map params = new HashMap(); - params.put(Patient.SP_GIVEN, new StringDt("testSearchStringParamWithNonNormalized_hora")); - List patients = toList(ourPatientDao.search(params)); - assertEquals(2, patients.size()); - - StringParam parameter = new StringParam("testSearchStringParamWithNonNormalized_hora"); - parameter.setExact(true); - params.put(Patient.SP_GIVEN, parameter); - patients = toList(ourPatientDao.search(params)); - assertEquals(0, patients.size()); - } @Test @@ -1086,6 +1152,41 @@ public class FhirResourceDaoTest { } + @Test + public void testTokenParamWhichIsTooLong() { + + String longStr1 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); + String longStr2 = RandomStringUtils.randomAlphanumeric(ResourceIndexedSearchParamString.MAX_LENGTH + 100); + + Organization org = new Organization(); + org.getName().setValue("testTokenParamWhichIsTooLong"); + org.getType().addCoding().setSystem(longStr1).setCode(longStr2); + + String subStr1 = longStr1.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + String subStr2 = longStr2.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + Set val = ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, subStr2)); + int initial = val.size(); + + ourOrganizationDao.create(org); + + val = ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, subStr2)); + assertEquals(initial + 1, val.size()); + + try { + ourOrganizationDao.searchForIds("type", new IdentifierDt(longStr1, subStr2)); + fail(); + } catch (InvalidRequestException e) { + // ok + } + + try { + ourOrganizationDao.searchForIds("type", new IdentifierDt(subStr1, longStr2)); + fail(); + } catch (InvalidRequestException e) { + // ok + } + } + @Test public void testUpdateAndGetHistoryResource() throws InterruptedException { Patient patient = new Patient(); @@ -1185,6 +1286,19 @@ public class FhirResourceDaoTest { } + @SuppressWarnings("unchecked") + private List toList(IBundleProvider theSearch) { + return (List) theSearch.getResources(0, theSearch.size()); + } + + private List toUnqualifiedVersionlessIds(IBundleProvider theFound) { + List retVal = new ArrayList(); + for (IResource next : theFound.getResources(0, theFound.size())) { + retVal.add(next.getId().toUnqualifiedVersionless()); + } + return retVal; + } + @AfterClass public static void afterClass() { ourCtx.close(); diff --git a/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml index 21995816613..055cd46ab63 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml +++ b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -65,7 +65,7 @@ - + diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js b/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js index 873f8b28df3..647513eb45b 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js +++ b/hapi-fhir-testpage-overlay/src/main/webapp/js/ClientCodeGeneratorHapi.js @@ -31,34 +31,34 @@ function generateHapiSearch(json, container) { var nextParam = json.params[i]; var paramLine = null; if (nextParam.type == 'string') { - paramLine = '.where(new StringParam("' + nextParam.name + '")'; + paramLine = '.where(new StringClientParam("' + nextParam.name + '")'; paramLine += nextParam.qualifier = ':exact' ? '.matchesExactly()' : '.matches()'; paramLine += '.value("' + nextParam.value + '"))'; } else if (nextParam.type == 'token') { var idx = nextParam.value.indexOf('|'); if (idx == -1) { - paramLine = '.where(new TokenParam("' + nextParam.name + '").exactly().code("' + nextParam.value + '"))'; + paramLine = '.where(new TokenClientParam("' + nextParam.name + '").exactly().code("' + nextParam.value + '"))'; } else { - paramLine = '.where(new TokenParam("' + nextParam.name + '").exactly().systemAndCode("' + nextParam.value.substring(0,idx) + '", "' + nextParam.value.substring(idx+1) + '"))'; + paramLine = '.where(new TokenClientParam("' + nextParam.name + '").exactly().systemAndCode("' + nextParam.value.substring(0,idx) + '", "' + nextParam.value.substring(idx+1) + '"))'; } } else if (nextParam.type == 'number') { - paramLine = '.where(new NumberParam("' + nextParam.name + '").exactly().value("' + nextParam.value + '"))'; + paramLine = '.where(new NumberClientParam("' + nextParam.name + '").exactly().value("' + nextParam.value + '"))'; } else if (nextParam.type == 'reference') { if (nextParam.qualifier == '') { if (nextParam.name.indexOf('.') == -1) { - paramLine = '.where(new ReferenceParam("' + nextParam.name + '").hasId("' + nextParam.value + '"))'; + paramLine = '.where(new ReferenceClientParam("' + nextParam.name + '").hasId("' + nextParam.value + '"))'; } } } else if (nextParam.type == 'date') { var dateQual = nextParam.value.indexOf('T') == -1 ? 'day' : 'second'; if (nextParam.value.substring(0,2) == '>=') { - paramLine = '.where(new DateParam("' + nextParam.name + '").afterOrEquals().' + dateQual + '("' + nextParam.value.substring(2) + '"))'; + paramLine = '.where(new DateClientParam("' + nextParam.name + '").afterOrEquals().' + dateQual + '("' + nextParam.value.substring(2) + '"))'; } else if (nextParam.value.substring(0,1) == '>') { - paramLine = '.where(new DateParam("' + nextParam.name + '").after().' + dateQual + '("' + nextParam.value.substring(1) + '"))'; + paramLine = '.where(new DateClientParam("' + nextParam.name + '").after().' + dateQual + '("' + nextParam.value.substring(1) + '"))'; } else if (nextParam.value.substring(0,2) == '<=') { - paramLine = '.where(new DateParam("' + nextParam.name + '").beforeOrEquals().' + dateQual + '("' + nextParam.value.substring(2) + '"))'; + paramLine = '.where(new DateClientParam("' + nextParam.name + '").beforeOrEquals().' + dateQual + '("' + nextParam.value.substring(2) + '"))'; } else if (nextParam.value.substring(0,1) == '<') { - paramLine = '.where(new DateParam("' + nextParam.name + '").before().' + dateQual + '("' + nextParam.value.substring(1) + '"))'; + paramLine = '.where(new DateClientParam("' + nextParam.name + '").before().' + dateQual + '("' + nextParam.value.substring(1) + '"))'; } } if (paramLine != null) { diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java index ed19f34c347..8a4990638cc 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java @@ -1,17 +1,12 @@ package ca.uhn.fhir.tinder; -import static org.apache.commons.lang.StringUtils.defaultString; - import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.http.ParseException; import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; @@ -25,8 +20,6 @@ import org.apache.maven.project.MavenProject; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; -import ca.uhn.fhir.tinder.model.BaseRootType; -import ca.uhn.fhir.tinder.model.Extension; import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingSpreadsheet; @Mojo(name = "generate-jparest-server", defaultPhase = LifecyclePhase.GENERATE_SOURCES) @@ -151,7 +144,7 @@ public class TinderJpaRestServerMojo extends AbstractMojo { TinderJpaRestServerMojo mojo = new TinderJpaRestServerMojo(); mojo.packageBase = "ca.uhn.test"; mojo.baseResourceNames = java.util.Collections.singletonList("observation"); - mojo.targetDirectory = new File("target/gen"); + mojo.targetDirectory = new File("target/generated/valuesets"); mojo.execute(); } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java index df46a0b2f33..11220dce24a 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/SearchParameter.java @@ -15,6 +15,7 @@ public class SearchParameter { private List myTargetTypes; private String myType; private List myCompositeOf; + private List myCompositeTypes; public SearchParameter() { @@ -124,4 +125,15 @@ public class SearchParameter { return myCompositeOf; } + public void setCompositeTypes(List theCompositeTypes) { + myCompositeTypes = theCompositeTypes; + } + + public List getCompositeTypes() { + if (myCompositeTypes == null) { + myCompositeTypes = new ArrayList(); + } + return myCompositeTypes; + } + } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java index f2221a8d408..c9ce31e768a 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureSpreadsheetParser.java @@ -14,13 +14,12 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.text.WordUtils; import org.apache.maven.plugin.MojoExecutionException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.tinder.model.AnyChild; import ca.uhn.fhir.tinder.model.BaseElement; import ca.uhn.fhir.tinder.model.BaseRootType; @@ -261,7 +260,8 @@ public abstract class BaseStructureSpreadsheetParser extends BaseStructureParser composite.setDescription(nextCompositeParam.getDescription()); composite.setPath(nextCompositeParam.getPath()); composite.setType("composite"); - composite.setCompositeOf(Arrays.asList(part1.getPath(), part2.getPath())); + composite.setCompositeOf(Arrays.asList(part1.getName(), part2.getName())); + composite.setCompositeTypes(Arrays.asList(WordUtils.capitalize(part1.getType()), WordUtils.capitalize(part2.getType()))); } } diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index df5a19acee5..46aa342cfd0 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -29,7 +29,7 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam @Description(shortDefinition="The resource identity") @OptionalParam(name="_id") StringParam theId, -#foreach ( $param in $searchParamsWithoutComposite ) #{if}(true) #{end} +#foreach ( $param in $searchParams ) #{if}(true) #{end} @Description(shortDefinition="${param.description}") #if (${param.type} == 'string' ) @@ -43,16 +43,16 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam DateRangeParam the${param.nameCapitalized}, #elseif (${param.type} == 'quantity' ) @OptionalParam(name="${param.name}") - QuantityDt the${param.nameCapitalized}, + QuantityAndListParam the${param.nameCapitalized}, #elseif (${param.type} == 'number' ) @OptionalParam(name="${param.name}") - QuantityDt the${param.nameCapitalized}, + NumberAndListParam the${param.nameCapitalized}, #elseif (${param.type} == 'reference' ) @OptionalParam(name="${param.name}", targetTypes={ #{foreach}($nextType in ${param.targetTypes}) ${nextType}.class #{if}($foreach.hasNext), #{end} #{end} } ) - ReferenceParam the${param.nameCapitalized}, + ReferenceAndListParam the${param.nameCapitalized}, #elseif (${param.type} == 'composite' ) - @OptionalParam(name="${param.name}") - ReferenceParam the${param.nameCapitalized}, + @OptionalParam(name="${param.name}", compositeTypes= { ${param.compositeTypes[0]}Param.class, ${param.compositeTypes[1]}Param.class }) + CompositeAndListParam<${param.compositeTypes[0]}Param, ${param.compositeTypes[1]}Param> the${param.nameCapitalized}, #end #end @@ -71,7 +71,7 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam try { SearchParameterMap paramMap = new SearchParameterMap(); paramMap.add("_id", theId); -#foreach ( $param in $searchParamsWithoutComposite ) +#foreach ( $param in $searchParams ) paramMap.add("${param.name}", the${param.nameCapitalized}); #end diff --git a/hapi-tinder-plugin/src/main/resources/vm/resource.vm b/hapi-tinder-plugin/src/main/resources/vm/resource.vm index 73c1fcc4d02..a0bdeaebea5 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/resource.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/resource.vm @@ -60,7 +60,11 @@ public class ${className} extends BaseResource implements IResource { * Path: ${param.path}
*

*/ +#if( ${param.typeCapitalized} == 'Composite' ) + public static final CompositeClientParam<${param.compositeTypes[0]}ClientParam, ${param.compositeTypes[1]}ClientParam> ${param.fluentConstantName} = new CompositeClientParam<${param.compositeTypes[0]}ClientParam, ${param.compositeTypes[1]}ClientParam>(${param.constantName}); +#else public static final ${param.typeCapitalized}ClientParam ${param.fluentConstantName} = new ${param.typeCapitalized}ClientParam(${param.constantName}); +#end #if( ${param.typeCapitalized} == 'Reference' ) #foreach ( $include in $param.paths ) diff --git a/restful-server-example/.settings/org.eclipse.wst.common.component b/restful-server-example/.settings/org.eclipse.wst.common.component index 74f1520df27..631bbc5a799 100644 --- a/restful-server-example/.settings/org.eclipse.wst.common.component +++ b/restful-server-example/.settings/org.eclipse.wst.common.component @@ -6,6 +6,12 @@ uses + + consumes + + + consumes + diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml index 71cd0d268f2..0e19a119b3e 100644 --- a/restful-server-example/pom.xml +++ b/restful-server-example/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.example restful-server-example - 0.3 + 0.5-SNAPSHOT war Sample RESTful Server (HAPI-FHIR) @@ -17,6 +17,15 @@ 0.5-SNAPSHOT + + + ca.uhn.hapi.fhir + hapi-fhir-testpage-overlay + 0.5-SNAPSHOT + war + provided + + @@ -48,6 +57,9 @@ + org.apache.maven.plugins maven-compiler-plugin @@ -57,13 +69,33 @@ 1.6 - - + + + + org.apache.maven.plugins + maven-war-plugin + + + + ca.uhn.hapi.fhir + hapi-fhir-testpage-overlay + + + + + + org.apache.maven.plugins maven-deploy-plugin - true + false