From 5675237f40bab9ffbc092d6f20f6404630afb571 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 20 May 2014 08:44:55 -0400 Subject: [PATCH] Lots more JPA work, and getting sorting in` --- .../java/ca/uhn/fhir/model/api/Bundle.java | 22 + .../fhir/model/api/IQueryParameterAnd.java | 2 +- .../uhn/fhir/model/api/IQueryParameterOr.java | 25 +- .../dstu/composite/ResourceReferenceDt.java | 3 +- .../java/ca/uhn/fhir/parser/ParserState.java | 29 +- .../java/ca/uhn/fhir/rest/api/SortSpec.java | 48 +- .../ca/uhn/fhir/rest/client/BaseClient.java | 17 +- .../uhn/fhir/rest/client/GenericClient.java | 6 + .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 2 + .../uhn/fhir/rest/gclient/ReferenceParam.java | 20 +- .../fhir/rest/method/QualifiedParamList.java | 13 + .../fhir/rest/method/SearchMethodBinding.java | 16 +- .../uhn/fhir/rest/param/CodingListParam.java | 7 +- .../uhn/fhir/rest/param/DateRangeParam.java | 9 +- .../ca/uhn/fhir/rest/param/IParamBinder.java | 3 +- .../fhir/rest/param/IdentifierListParam.java | 7 +- .../ca/uhn/fhir/rest/param/ParameterUtil.java | 34 +- .../rest/param/QueryParameterAndBinder.java | 5 +- .../rest/param/QueryParameterOrBinder.java | 4 +- .../rest/param/QueryParameterTypeBinder.java | 7 +- .../uhn/fhir/rest/param/ReferenceParam.java | 62 +- .../uhn/fhir/rest/param/SearchParameter.java | 14 +- .../ca/uhn/fhir/rest/param/SortParameter.java | 72 +- .../ca/uhn/fhir/rest/param/StringBinder.java | 5 +- .../uhn/fhir/rest/server/RestfulServer.java | 48 +- .../provider/ServerConformanceProvider.java | 38 +- .../RestfulPatientResourceProviderMore.java | 31 +- .../src/site/xdoc/doc_rest_operations.xml | 43 ++ .../composite/ResourceReferenceDtTest.java | 100 +++ .../fhir/rest/client/ReferenceClientTest.java | 129 ++++ .../rest/server/ReferenceParameterTest.java | 165 +++++ .../fhir/rest/server/ResourceMethodTest.java | 4 +- ...ava => ServerConformanceProviderTest.java} | 57 +- .../ca/uhn/fhir/rest/server/SortTest.java | 177 +++++ hapi-fhir-jpaserver-base/.classpath | 301 ++++---- hapi-fhir-jpaserver-base/.gitignore | 1 + hapi-fhir-jpaserver-base/pom.xml | 53 ++ .../ca/uhn/fhir/jpa/dao/FhirResourceDao.java | 657 ++++++++++-------- .../uhn/fhir/jpa/dao/SearchParameterMap.java | 18 +- .../fhir/jpa/entity/BaseResourceTable.java | 53 +- .../fhir/jpa/entity/ResourceHistoryTag.java | 3 +- .../ResourceIndexedSearchParamDate.java | 6 +- .../ResourceIndexedSearchParamNumber.java | 6 +- .../ResourceIndexedSearchParamString.java | 6 +- .../ResourceIndexedSearchParamToken.java | 9 +- .../ca/uhn/fhir/jpa/entity/ResourceTag.java | 3 +- .../uhn/fhir/jpa/dao/FhirResourceDaoTest.java | 48 +- .../jpa/testentity}/DeviceResourceTable.java | 3 +- .../DiagnosticReportResourceTable.java | 3 +- .../testentity}/LocationResourceTable.java | 3 +- .../testentity}/ObservationResourceTable.java | 3 +- .../OrganizationResourceTable.java | 3 +- .../jpa/testentity}/PatientResourceTable.java | 3 +- .../QuestionnaireResourceTable.java | 3 +- .../test/resources/META-INF/persistence.xml | 50 ++ ...ml => fhir-jpabase-spring-test-config.xml} | 12 +- hapi-fhir-jpaserver-test/.classpath | 1 + hapi-fhir-jpaserver-test/pom.xml | 27 + .../test/CompleteResourceProviderTest.java | 177 +++++ .../java/ca/uhn/fhir/jpa/test/JpaTestApp.java | 1 + .../jpa/test/PatientResourceProvider.java | 46 -- .../resources/fhir-spring-test-config.xml | 17 +- .../resources/fhir_jpatest_persistence.xml | 17 +- .../fhir/tinder/TinderJpaRestServerMojo.java | 16 +- .../uhn/fhir/tinder/model/BaseRootType.java | 10 + .../tinder/parser/BaseStructureParser.java | 19 +- .../resources/vm/jpa_resource_provider.vm | 8 +- .../main/resources/vm/jpa_resource_table.vm | 6 +- 68 files changed, 2123 insertions(+), 693 deletions(-) create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDtTest.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ReferenceClientTest.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java rename hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/{DocumentationTest.java => ServerConformanceProviderTest.java} (56%) create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SortTest.java rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/DeviceResourceTable.java (78%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/DiagnosticReportResourceTable.java (81%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/LocationResourceTable.java (79%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/ObservationResourceTable.java (80%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/OrganizationResourceTable.java (80%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/PatientResourceTable.java (79%) rename hapi-fhir-jpaserver-base/src/{main/java/ca/uhn/fhir/jpa/entity => test/java/ca/uhn/fhir/jpa/testentity}/QuestionnaireResourceTable.java (80%) create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml rename hapi-fhir-jpaserver-base/src/test/resources/{fhir-spring-test-config.xml => fhir-jpabase-spring-test-config.xml} (85%) create mode 100644 hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java delete mode 100644 hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/PatientResourceProvider.java rename hapi-fhir-jpaserver-base/src/main/resources/META-INF/persistence.xml => hapi-fhir-jpaserver-test/src/main/resources/fhir_jpatest_persistence.xml (65%) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java index ad7f2fb4a0e..0fd89429b7a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/Bundle.java @@ -166,4 +166,26 @@ public class Bundle extends BaseBundle /*implements IElement*/ { return retVal; } + /** + * Returns the number of entries in this bundle + */ + public int size() { + return getEntries().size(); + } + + /** + * Returns a list containing all resources of the given type from this bundle + */ + public List getResources(Class theClass) { + ArrayList retVal = new ArrayList(); + for (BundleEntry next : getEntries()) { + if (next.getResource()!=null && theClass.isAssignableFrom(next.getResource().getClass())) { + @SuppressWarnings("unchecked") + T resource = (T) next.getResource(); + retVal.add(resource); + } + } + return retVal; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java index ca7193c67ab..5d2d8bb87e2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java @@ -45,7 +45,7 @@ public interface IQueryParameterAnd { * for information on the token format *

*/ - public List getValuesAsQueryTokens(); + public List getValuesAsQueryTokens(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java index 62856f35fda..588200a0893 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java @@ -20,32 +20,33 @@ package ca.uhn.fhir.model.api; * #L% */ +import java.util.List; + import ca.uhn.fhir.rest.method.QualifiedParamList; public interface IQueryParameterOr { /** - * Sets the value of this type using the token format. This - * format is used in HTTP queries as a parameter format. + * Sets the value of this type using the token format. This format is used in HTTP queries as a parameter + * format. *

- * See FHIR specification - * 2.2.2 Search SearchParameter Types - * for information on the token format + * See FHIR specification 2.2.2 Search + * SearchParameter Types for information on the token format *

*/ public void setValuesAsQueryTokens(QualifiedParamList theParameters); + // public void setValuesAsQueryTokens(List theParameters); + /** - * Returns the value of this type using the token format. This - * format is used in HTTP queries as a parameter format. + * Returns the value of this type using the token format. This format is used in HTTP queries as a parameter + * format. * *

- * See FHIR specification - * 2.2.2 Search SearchParameter Types - * for information on the token format + * See FHIR specification 2.2.2 Search + * SearchParameter Types for information on the token format *

*/ - public QualifiedParamList getValuesAsQueryTokens(); + public List getValuesAsQueryTokens(); - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java index 65b6ae07262..fe8572188bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java @@ -175,8 +175,7 @@ public class ResourceReferenceDt *

*/ public ResourceReferenceDt setReference( String theString) { - myReference = new StringDt(theString); - return this; + return setReference(new StringDt(theString)); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index b9106195ab6..59b582b2bd7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.parser; import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import java.io.ObjectInputStream.GetField; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -39,6 +40,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition; +import ca.uhn.fhir.context.RuntimeChildResourceDefinition; import ca.uhn.fhir.context.RuntimeElemContainedResources; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition; @@ -663,7 +665,7 @@ class ParserState { case RESOURCE_REF: { ResourceReferenceDt newChildInstance = new ResourceReferenceDt(); myDefinition.getMutator().addValue(myParentInstance, newChildInstance); - ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), newChildInstance); + ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), (RuntimeResourceReferenceDefinition)target, newChildInstance); push(newState); return; } @@ -767,7 +769,7 @@ class ParserState { ResourceReferenceDt newChildInstance = new ResourceReferenceDt(); getPreResourceState().getResourceReferences().add(newChildInstance); child.getMutator().addValue(myInstance, newChildInstance); - ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), newChildInstance); + ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), resourceRefTarget, newChildInstance); push(newState); return; } @@ -873,7 +875,7 @@ class ParserState { case RESOURCE_REF: { ResourceReferenceDt newChildInstance = new ResourceReferenceDt(); myExtension.setValue(newChildInstance); - ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), newChildInstance); + ResourceReferenceState newState = new ResourceReferenceState(getPreResourceState(), null, newChildInstance); push(newState); return; } @@ -1125,7 +1127,7 @@ class ParserState { private ResourceReferenceDt myInstance; private ResourceReferenceSubState mySubState; - public ResourceReferenceState(PreResourceState thePreResourceState, ResourceReferenceDt theInstance) { + public ResourceReferenceState(PreResourceState thePreResourceState, RuntimeResourceReferenceDefinition theTarget, ResourceReferenceDt theInstance) { super(thePreResourceState); myInstance = theInstance; mySubState = ResourceReferenceSubState.INITIAL; @@ -1144,6 +1146,25 @@ class ParserState { case INITIAL: throw new DataFormatException("Unexpected attribute: " + theValue); case REFERENCE: + int lastSlash = theValue.lastIndexOf('/'); + if (lastSlash==-1) { + myInstance.setResourceId(theValue); + } else if (lastSlash==0) { + myInstance.setResourceId(theValue.substring(1)); + }else { + int secondLastSlash=theValue.lastIndexOf('/', lastSlash-1); + String resourceTypeName; + if (secondLastSlash==-1) { + resourceTypeName=theValue.substring(0,lastSlash); + }else { + resourceTypeName=theValue.substring(secondLastSlash+1,lastSlash); + } + myInstance.setResourceId(theValue.substring(lastSlash+1)); + RuntimeResourceDefinition def = myContext.getResourceDefinition(resourceTypeName); + if(def!=null) { + myInstance.setResourceType(def.getImplementingClass()); + } + } myInstance.setReference(theValue); break; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SortSpec.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SortSpec.java index b9cec2e10f9..f98290669a9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SortSpec.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SortSpec.java @@ -21,35 +21,23 @@ package ca.uhn.fhir.rest.api; */ /** - * Represents values for sorting - * resources returned by a server. + * Represents values for sorting resources + * returned by a server. */ public class SortSpec { - private String myFieldName; private SortSpec myChain; + private String myFieldName; + private SortOrderEnum myOrder; /** - * Gets the chained sort specification, or null if none. If - * multiple sort parameters are chained (indicating a sub-sort), the second - * level sort is chained via this property. + * Gets the chained sort specification, or null if none. If multiple sort parameters are chained + * (indicating a sub-sort), the second level sort is chained via this property. */ public SortSpec getChain() { return myChain; } - /** - * Sets the chained sort specification, or null if none. If - * multiple sort parameters are chained (indicating a sub-sort), the second - * level sort is chained via this property. - */ - public void setChain(SortSpec theChain) { - myChain = theChain; - } - - private SortOrderEnum myOrder; - /** * Returns the actual name of the field to sort by */ @@ -58,16 +46,22 @@ public class SortSpec { } /** - * Returns the sort order specified by this parameter, or null - * if none is explicitly defined (which means {@link SortOrderEnum#ASC} - * according to the FHIR - * specification) + * Returns the sort order specified by this parameter, or null if none is explicitly defined (which + * means {@link SortOrderEnum#ASC} according to the FHIR specification) */ public SortOrderEnum getOrder() { return myOrder; } + /** + * Sets the chained sort specification, or null if none. If multiple sort parameters are chained + * (indicating a sub-sort), the second level sort is chained via this property. + */ + public void setChain(SortSpec theChain) { + myChain = theChain; + } + /** * Sets the actual name of the field to sort by */ @@ -76,11 +70,9 @@ public class SortSpec { } /** - * Sets the sort order specified by this parameter, or null if - * none is explicitly defined (which means {@link SortOrderEnum#ASC} - * according to the FHIR - * specification) + * Sets the sort order specified by this parameter, or null if none is explicitly defined (which means + * {@link SortOrderEnum#ASC} according to the FHIR specification) */ public void setOrder(SortOrderEnum theOrder) { myOrder = theOrder; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 55eb1263ac0..6d3ebb17ff7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -154,16 +154,25 @@ public abstract class BaseClient { } if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) { - BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase()); - + String body=null; try { - String body = IOUtils.toString(reader); - exception.setResponseBody(body); + body = IOUtils.toString(reader); } catch (Exception e) { ourLog.debug("Failed to read input stream", e); } finally { IOUtils.closeQuietly(reader); } + + String message = "HTTP " + response.getStatusLine().getStatusCode()+" " +response.getStatusLine().getReasonPhrase(); + if (Constants.CT_TEXT.equals(mimeType)) { + message = message+": " + body; + } + + BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message); + + if(body!=null) { + exception.setResponseBody(body); + } throw exception; } 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 b7e4d9bf854..7c5e731e7da 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 @@ -431,6 +431,12 @@ public class GenericClient extends BaseClient implements IGenericClient { params.get(parameterName).add(parameterValue); } + @Override + public IQuery prettyPrint() { + setPrettyPrint(true); + return this; + } + } private class QueryInternal implements IUntypedQuery { 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 c24b28a1085..96a5db51cec 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 @@ -45,5 +45,7 @@ public interface IQuery { * can be useful for debugging, but is generally not desirable in a production situation. */ IQuery andLogRequestAndResponse(boolean theLogRequestAndResponse); + + IQuery prettyPrint(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceParam.java index 6b74e82f529..2d7be146972 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ReferenceParam.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.gclient; +import ca.uhn.fhir.model.primitive.IdDt; + /* * #%L * HAPI FHIR Library @@ -38,6 +40,14 @@ public class ReferenceParam implements IParam { return new ReferenceChainCriterion(getParamName(), theICriterion); } + /** + * 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.getValueAsString()); + } + /** * Match the referenced resource if the resource has the given ID (this can be * the logical ID or the absolute URL of the resource) @@ -48,8 +58,8 @@ public class ReferenceParam implements IParam { private static class ReferenceChainCriterion implements ICriterion, ICriterionInternal { - private ICriterionInternal myWrappedCriterion; private String myParamName; + private ICriterionInternal myWrappedCriterion; public ReferenceChainCriterion(String theParamName, ICriterion theWrappedCriterion) { myParamName = theParamName; @@ -57,13 +67,13 @@ public class ReferenceParam implements IParam { } @Override - public String getParameterValue() { - return myWrappedCriterion.getParameterValue(); + public String getParameterName() { + return myParamName + "." + myWrappedCriterion.getParameterName(); } @Override - public String getParameterName() { - return myParamName + "." + myWrappedCriterion.getParameterName(); + public String getParameterValue() { + return myWrappedCriterion.getParameterValue(); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QualifiedParamList.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QualifiedParamList.java index 82f40ed6d5e..47d7efad400 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QualifiedParamList.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QualifiedParamList.java @@ -23,6 +23,10 @@ package ca.uhn.fhir.rest.method; import java.util.ArrayList; import java.util.StringTokenizer; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; + public class QualifiedParamList extends ArrayList { private static final long serialVersionUID = 1L; @@ -37,6 +41,15 @@ public class QualifiedParamList extends ArrayList { super(theCapacity); } + public QualifiedParamList(FhirContext theContext, IQueryParameterOr theNextOr) { + for (IQueryParameterType next : theNextOr.getValuesAsQueryTokens()) { + if (myQualifier==null) { + myQualifier=next.getQueryParameterQualifier(theContext); + } + add(next.getValueAsQueryToken()); + } + } + public String getQualifier() { return myQualifier; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 5a7605e214a..a9f1cdf4bf3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -159,6 +159,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { return false; } + } else { + methodParamsTemp.add(name); } } if (myQueryName != null) { @@ -181,11 +183,17 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { methodParamsTemp.add(next); } } - boolean retVal = methodParamsTemp.containsAll(theRequest.getParameters().keySet()); + Set keySet = theRequest.getParameters().keySet(); + for (String next : keySet) { + if (next.startsWith("_")) { + continue; + } + if (!methodParamsTemp.contains(next)) { + return false; + } + } - ourLog.trace("Method {} matches: {}", getMethod().getName(), retVal); - - return retVal; + return true; } public void setResourceType(Class resourceType) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CodingListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CodingListParam.java index 66106583301..1744cd7742d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CodingListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/CodingListParam.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.List; import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.rest.method.QualifiedParamList; @@ -97,10 +98,10 @@ public class CodingListParam implements IQueryParameterOr, Iterable { } @Override - public QualifiedParamList getValuesAsQueryTokens() { - QualifiedParamList retVal = new QualifiedParamList(); + public List getValuesAsQueryTokens() { + ArrayList retVal = new ArrayList(); for (CodingDt next : myCodings) { - retVal.add(next.getValueAsQueryToken()); + retVal.add(next); } return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java index 825708be6fc..987d3d8ada1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/DateRangeParam.java @@ -25,6 +25,7 @@ import java.util.Date; import java.util.List; import ca.uhn.fhir.model.api.IQueryParameterAnd; +import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.method.QualifiedParamList; @@ -156,13 +157,13 @@ public class DateRangeParam implements IQueryParameterAnd { } @Override - public List getValuesAsQueryTokens() { - ArrayList retVal = new ArrayList(); + public List getValuesAsQueryTokens() { + ArrayList retVal = new ArrayList(); if (myLowerBound != null) { - retVal.add(QualifiedParamList.singleton(myLowerBound.getValueAsQueryToken())); + retVal.add(ParameterUtil.singleton(myLowerBound)); } if (myUpperBound != null) { - retVal.add(QualifiedParamList.singleton(myUpperBound.getValueAsQueryToken())); + retVal.add(ParameterUtil.singleton(myUpperBound)); } return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParamBinder.java index c23d30ae36a..61b5e16dc84 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParamBinder.java @@ -23,13 +23,14 @@ package ca.uhn.fhir.rest.param; import java.util.List; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; interface IParamBinder { - List encode(FhirContext theContext, Object theString) throws InternalErrorException; + List encode(FhirContext theContext, Object theString) throws InternalErrorException; Object parse(List theList) throws InternalErrorException, InvalidRequestException; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java index 23655554bbc..46103f4d90e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IdentifierListParam.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.rest.method.QualifiedParamList; @@ -39,10 +40,10 @@ public class IdentifierListParam implements IQueryParameterOr { } @Override - public QualifiedParamList getValuesAsQueryTokens() { - QualifiedParamList retVal = new QualifiedParamList(); + public List getValuesAsQueryTokens() { + ArrayList retVal = new ArrayList(); for (IdentifierDt next : myIdentifiers) { - retVal.add(next.getValueAsQueryToken()); + retVal.add(next); } return retVal; } 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 71013a8f2ac..80d9af19da5 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 @@ -41,6 +41,8 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.time.DateUtils; import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.model.api.IQueryParameterOr; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.PathSpecification; import ca.uhn.fhir.model.api.TagList; @@ -56,7 +58,9 @@ import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ServerBase; import ca.uhn.fhir.rest.annotation.Since; +import ca.uhn.fhir.rest.annotation.Sort; import ca.uhn.fhir.rest.annotation.VersionIdParam; +import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.util.ReflectionUtil; public class ParameterUtil { @@ -69,7 +73,7 @@ public class ParameterUtil { intTypes.add(IntegerDt.class); intTypes.add(Integer.class); BINDABLE_INTEGER_TYPES = Collections.unmodifiableSet(intTypes); - + HashSet> timeTypes = new HashSet>(); timeTypes.add(InstantDt.class); timeTypes.add(Date.class); @@ -135,6 +139,7 @@ public class ParameterUtil { public static Set> getBindableInstantTypes() { return BINDABLE_TIME_TYPES; } + public static Set> getBindableIntegerTypes() { return BINDABLE_INTEGER_TYPES; } @@ -153,7 +158,7 @@ public class ParameterUtil { Class> innerCollectionType = null; if (TagList.class.isAssignableFrom(parameterType)) { // TagList is handled directly within the method bindings - param=new NullParameter(); + param = new NullParameter(); } else { if (Collection.class.isAssignableFrom(parameterType)) { innerCollectionType = (Class>) parameterType; @@ -218,6 +223,8 @@ public class ParameterUtil { param = new SinceParameter(); } else if (nextAnnotation instanceof Count) { param = new CountParameter(); + } else if (nextAnnotation instanceof Sort) { + param = new SortParameter(); } else { continue; } @@ -227,7 +234,7 @@ public class ParameterUtil { } if (param == null) { - throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + throw new ConfigurationException("Parameter #" + ((paramIndex+1))+"/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter"); } @@ -290,4 +297,25 @@ public class ParameterUtil { return null; } + public static IQueryParameterOr singleton(final IQueryParameterType theParam) { + return new IQueryParameterOr() { + + @Override + public void setValuesAsQueryTokens(QualifiedParamList theParameters) { + if (theParameters.isEmpty()) { + return; + } + if (theParameters.size() > 1) { + throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); + } + theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0)); + } + + @Override + public List getValuesAsQueryTokens() { + return Collections.singletonList(theParam); + } + }; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterAndBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterAndBinder.java index 0844abe64d6..115cfa3cc1f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterAndBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterAndBinder.java @@ -24,6 +24,7 @@ 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.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -36,8 +37,8 @@ final class QueryParameterAndBinder implements IParamBinder { } @Override - public List encode(FhirContext theContext, Object theString) throws InternalErrorException { - List retVal = ((IQueryParameterAnd) theString).getValuesAsQueryTokens(); + public List encode(FhirContext theContext, Object theString) throws InternalErrorException { + List retVal = ((IQueryParameterAnd) theString).getValuesAsQueryTokens(); return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterOrBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterOrBinder.java index 6ebf68fe55c..db76b4d51c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterOrBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterOrBinder.java @@ -37,8 +37,8 @@ final class QueryParameterOrBinder implements IParamBinder { } @Override - public List encode(FhirContext theContext, Object theString) throws InternalErrorException { - QualifiedParamList retVal = ((IQueryParameterOr) theString).getValuesAsQueryTokens(); + public List encode(FhirContext theContext, Object theString) throws InternalErrorException { + IQueryParameterOr retVal = ((IQueryParameterOr) theString); return Collections.singletonList(retVal); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterTypeBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterTypeBinder.java index c08e7c0e5d7..d5529ca31bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterTypeBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QueryParameterTypeBinder.java @@ -24,6 +24,7 @@ import java.util.Collections; 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.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -37,11 +38,9 @@ final class QueryParameterTypeBinder implements IParamBinder { } @Override - public List encode(FhirContext theContext, Object theString) throws InternalErrorException { + public List encode(FhirContext theContext, Object theString) throws InternalErrorException { IQueryParameterType param = (IQueryParameterType) theString; - String retVal = param.getValueAsQueryToken(); - String qualifier=param.getQueryParameterQualifier(theContext); - return Collections.singletonList(QualifiedParamList.singleton(qualifier, retVal)); + return Collections.singletonList(ParameterUtil.singleton(param)); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java index 5b19e43fd02..b9c5fd9c1e9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.param; * #L% */ +import static org.apache.commons.lang3.StringUtils.*; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; @@ -27,7 +28,7 @@ import ca.uhn.fhir.model.api.IResource; public class ReferenceParam implements IQueryParameterType { private String myChain; - private Class myType; + private String myResourceType; private String myValue; public ReferenceParam() { @@ -42,8 +43,8 @@ public class ReferenceParam implements IQueryParameterType { setChain(theChain); } - public ReferenceParam(Class theType, String theChain, String theValue) { - setType(theType); + public ReferenceParam(String theResourceType, String theChain, String theValue) { + setResourceType(theResourceType); setValueAsQueryToken(null, theValue); setChain(theChain); } @@ -52,8 +53,25 @@ public class ReferenceParam implements IQueryParameterType { return myChain; } - public Class getType() { - return myType; + @Override + public String getQueryParameterQualifier(FhirContext theContext) { + StringBuilder b = new StringBuilder(); + if (isNotBlank(myResourceType)) { + b.append(':'); + b.append(myResourceType); + } + if (isNotBlank(myChain)) { + b.append('.'); + b.append(myChain); + } + if (b.length() != 0) { + return b.toString(); + } + return null; + } + + public String getResourceType() { + return myResourceType; } @Override @@ -65,21 +83,39 @@ public class ReferenceParam implements IQueryParameterType { myChain = theChain; } - public void setType(Class theType) { - myType = theType; + public Class getResourceType(FhirContext theCtx) { + if (isBlank(myResourceType)) { + return null; + } + return theCtx.getResourceDefinition(myResourceType).getImplementingClass(); + } + + public void setResourceType(String theResourceType) { + myResourceType = theResourceType; } @Override public void setValueAsQueryToken(String theQualifier, String theValue) { + String q = theQualifier; + if (isNotBlank(q)) { + if (q.startsWith(":")) { + int nextIdx = q.indexOf('.'); + if (nextIdx != -1) { + myResourceType = q.substring(1, nextIdx); + myChain = q.substring(nextIdx + 1); + } else { + myResourceType = q.substring(1); + } + } else if (q.startsWith(".")) { + myChain = q.substring(1); + } + } + myValue = theValue; } - @Override - public String getQueryParameterQualifier(FhirContext theContext) { - if (myType != null) { - return ":" + theContext.getResourceDefinition(myType).getName(); - } - return null; + public String getValue() { + return myValue; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java index 6829a688bc5..2546a9af512 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.param; * #L% */ +import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -63,7 +64,14 @@ public class SearchParameter extends BaseQueryParameter { */ @Override public List encode(FhirContext theContext, Object theObject) throws InternalErrorException { - return myParamBinder.encode(theContext, theObject); + ArrayList retVal = new ArrayList(); + + List val = myParamBinder.encode(theContext, theObject); + for (IQueryParameterOr nextOr : val) { + retVal.add(new QualifiedParamList(theContext, nextOr)); + } + + return retVal; } public String getDescription() { @@ -131,7 +139,7 @@ public class SearchParameter extends BaseQueryParameter { } else if (IQueryParameterAnd.class.isAssignableFrom(type)) { myParamBinder = new QueryParameterAndBinder((Class) type); } else if (String.class.equals(type)) { - myParamBinder=new StringBinder(); + myParamBinder = new StringBinder(); } else { throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName()); } @@ -150,6 +158,8 @@ public class SearchParameter extends BaseQueryParameter { myParamType = SearchParamTypeEnum.TOKEN; } else if (QuantityDt.class.isAssignableFrom(type)) { myParamType = SearchParamTypeEnum.QUANTITY; + } else if (ReferenceParam.class.isAssignableFrom(type)) { + myParamType = SearchParamTypeEnum.REFERENCE; } else { throw new ConfigurationException("Unknown search parameter type: " + type); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SortParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SortParameter.java index 3bebfd73439..be156c820d4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SortParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SortParameter.java @@ -20,7 +20,10 @@ package ca.uhn.fhir.rest.param; * #L% */ +import static org.apache.commons.lang3.StringUtils.*; + import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; @@ -41,41 +44,82 @@ public class SortParameter implements IParameter { @Override public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, BaseClientInvocation theClientInvocation) throws InternalErrorException { SortSpec ss = (SortSpec) theSourceClientArgument; - if (ss ==null) { + if (ss == null) { return; } String name; - if (ss.getOrder()==null) { + if (ss.getOrder() == null) { name = Constants.PARAM_SORT; - }else if (ss.getOrder() == SortOrderEnum.ASC) { + } else if (ss.getOrder() == SortOrderEnum.ASC) { name = Constants.PARAM_SORT_ASC; - }else { + } else { name = Constants.PARAM_SORT_DESC; } - + if (ss.getFieldName() != null) { if (!theTargetQueryArguments.containsKey(name)) { - // TODO: implement + theTargetQueryArguments.put(name, new ArrayList()); } + theTargetQueryArguments.get(name).add(ss.getFieldName()); } - + } @Override public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { - // TODO Auto-generated method stub - return null; + if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT)) { + if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_ASC)) { + if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_DESC)) { + return null; + } + } + } + + SortSpec outerSpec = null; + SortSpec innerSpec = null; + for (String nextParamName : theRequest.getParameters().keySet()) { + SortOrderEnum order; + if (Constants.PARAM_SORT.equals(nextParamName)) { + order = null; + } else if (Constants.PARAM_SORT_ASC.equals(nextParamName)) { + order = SortOrderEnum.ASC; + } else if (Constants.PARAM_SORT_DESC.equals(nextParamName)) { + order = SortOrderEnum.DESC; + } else { + continue; + } + + String[] values = theRequest.getParameters().get(nextParamName); + if (values != null) { + for (String nextValue : values) { + if (isNotBlank(nextValue)) { + SortSpec spec = new SortSpec(); + spec.setOrder(order); + spec.setFieldName(nextValue); + if (innerSpec == null) { + outerSpec = spec; + innerSpec = spec; + } else { + innerSpec.setChain(spec); + innerSpec = spec; + } + } + } + } + } + + return outerSpec; } @Override public void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType) { - if (theOuterCollectionType != null) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Sort.class.getName() + " but can not be of collection type"); + if (theOuterCollectionType != null || theInnerCollectionType!=null) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Sort.class.getName() + " but can not be of collection type"); } - if (!ParameterUtil.getBindableInstantTypes().contains(theParameterType)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Sort.class.getName() + " but is an invalid type, must be: " + SortSpec.class.getCanonicalName()); + if (!theParameterType.equals(SortSpec.class)) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '"+theMethod.getDeclaringClass().getCanonicalName() + "' is annotated with @" + Sort.class.getName() + " but is an invalid type, must be: " + SortSpec.class.getCanonicalName()); } + } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringBinder.java index f1014be89f8..bf70a812b45 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/StringBinder.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IQueryParameterOr; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -33,9 +34,9 @@ final class StringBinder implements IParamBinder { } @Override - public List encode(FhirContext theContext, Object theString) throws InternalErrorException { + public List encode(FhirContext theContext, Object theString) throws InternalErrorException { String retVal = ((String) theString); - return Collections.singletonList(QualifiedParamList.singleton(retVal)); + return Collections.singletonList(ParameterUtil.singleton(new StringParam(retVal))); } @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 10239ddb8f7..c3ffd9cf8ef 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 @@ -360,31 +360,35 @@ public class RestfulServer extends HttpServlet { private void findResourceMethods(Object theProvider, Class clazz) { for (Method m : clazz.getDeclaredMethods()) { - if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) { - ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); + if (!Modifier.isPublic(m.getModifiers())) { + ourLog.debug("Ignoring non-public method: {}",m); + } else { + if (!Modifier.isStatic(m.getModifiers())) { + ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); - BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider); - if (foundMethodBinding != null) { + BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider); + if (foundMethodBinding != null) { - String resourceName = foundMethodBinding.getResourceName(); - ResourceBinding resourceBinding; - if (resourceName == null) { - resourceBinding = myNullResourceBinding; - } else { - RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName); - if (myResourceNameToProvider.containsKey(definition.getName())) { - resourceBinding = myResourceNameToProvider.get(definition.getName()); + String resourceName = foundMethodBinding.getResourceName(); + ResourceBinding resourceBinding; + if (resourceName == null) { + resourceBinding = myNullResourceBinding; } else { - resourceBinding = new ResourceBinding(); - resourceBinding.setResourceName(resourceName); - myResourceNameToProvider.put(resourceName, resourceBinding); + RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName); + if (myResourceNameToProvider.containsKey(definition.getName())) { + resourceBinding = myResourceNameToProvider.get(definition.getName()); + } else { + resourceBinding = new ResourceBinding(); + resourceBinding.setResourceName(resourceName); + myResourceNameToProvider.put(resourceName, resourceBinding); + } } - } - resourceBinding.addMethod(foundMethodBinding); - ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); - } else { - ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName()); + resourceBinding.addMethod(foundMethodBinding); + ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); + } else { + ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName()); + } } } } @@ -535,7 +539,7 @@ public class RestfulServer extends HttpServlet { } else { resourceBinding = myResourceNameToProvider.get(resourceName); if (resourceBinding == null) { - throw new ResourceNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet()); + throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet()); } } @@ -614,7 +618,7 @@ public class RestfulServer extends HttpServlet { resourceMethod = resourceBinding.getMethod(r); } if (null == resourceMethod) { - throw new ResourceNotFoundException("No resource method available for the supplied parameters " + params); + throw new InvalidRequestException("No resource method available for the supplied parameters " + params); } resourceMethod.invokeServer(this, r, theResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java index ed40aa542d8..3061df6d07d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.server.provider; * #L% */ +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -89,7 +90,7 @@ public class ServerConformanceProvider { resource.getProfile().setId(new IdDt(def.getResourceProfile())); Map nameToSearchParam = new HashMap(); - for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { + for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { RestfulOperationTypeEnum resOp = nextMethodBinding.getResourceOperationType(); if (resOp != null) { if (resourceOps.contains(resOp) == false) { @@ -108,19 +109,34 @@ public class ServerConformanceProvider { if (nextMethodBinding instanceof SearchMethodBinding) { List params = ((SearchMethodBinding) nextMethodBinding).getParameters(); - // TODO: this would probably work best if we sorted these by - // required first, then optional - + List searchParameters = new ArrayList(); + for (IParameter nextParameter : params) { + if ((nextParameter instanceof SearchParameter)) { + searchParameters.add((SearchParameter) nextParameter); + } + } + Collections.sort(searchParameters, new Comparator() { + @Override + public int compare(SearchParameter theO1, SearchParameter theO2) { + if (theO1.isRequired() == theO2.isRequired()) { + return 0; + } + if (theO1.isRequired()) { + return 1; + } + return -1; + } + }); + if (searchParameters.isEmpty()) { + continue; + } + boolean allOptional = searchParameters.get(0).isRequired()==false; + RestResourceSearchParam searchParam = null; ExtensionDt searchParamChain = null; - for (IParameter nextParameterObj : params) { - if (!(nextParameterObj instanceof SearchParameter)) { - continue; - } + for (SearchParameter nextParameter : searchParameters) { - SearchParameter nextParameter = (SearchParameter) nextParameterObj; - - if (searchParam == null) { + if (searchParam == null || allOptional) { if (!nameToSearchParam.containsKey(nextParameter.getName())) { RestResourceSearchParam param = resource.addSearchParam(); param.setName(nextParameter.getName()); 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 8e33aa0c9fc..4b9d6f2bb59 100644 --- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java +++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java @@ -59,6 +59,7 @@ import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.param.CodingListParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QualifiedDateParam; +import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -90,6 +91,34 @@ public List findPatients( } //END SNIPPET: underlyingReq +//START SNIPPET: reference +@Search +public List findPatients( + @RequiredParam(name=Patient.SP_PROVIDER) ReferenceParam theProvider + ) { + +// May be populated with the resource type (e.g. "Patient") if the client requested one +String type = theProvider.getResourceType(); + +// May be populated with the chain (e.g. "name") if the client requested one +String chain = theProvider.getChain(); + +/* The actual parameter value. This will be the resource ID if no chain was provided, + * but refers to the value of a specific property noted by the chain if one was given. + * For example, the following request: + * http://example.com/fhir/Patient?provider:Organization.name=FooOrg + * has a type of "Organization" and a chain of "name", meaning that + * the returned patients should have a provider which is an Organization + * with a name matching "FooOrg". + */ +String value = theProvider.getValue(); + +List retVal=new ArrayList(); // populate this +return retVal; + +} +//END SNIPPET: reference + //START SNIPPET: read @Read() @@ -557,7 +586,7 @@ private interface IPatientClient extends IBasicClient // Only one method is shown here, but many methods may be // added to the same client interface! } -//START SNIPPET: clientReadInterface +//END SNIPPET: clientReadInterface public void clientRead() { //START SNIPPET: clientReadTags 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 134d17068eb..93b7496be15 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml @@ -693,6 +693,49 @@ + + +

+ Many search parameters refer to resource references. For instance, the Patient + parameter "provider" refers to the resource marked as the managing organization + for patients. +

+

+ Reference parameters use the + ReferenceParam + type. Reference param objects being passed into server methods will have three properties + populated: +

+
    +
  • + The resource type (optional): This is the type of resource + being targeted, if the client specifies one in the URL. +
  • +
  • + The chain property (optional): This is the name of the + property on the target resource to search for +
  • +
  • + The value (required): This is the actual value to search for. If + a chain property has not been specified, this is the ID of the resource. +
  • +
+ + + + + + +

+ Example URLs to invoke this method: +
+ Resource by ID: http://fhir.example.com/Patient?provider=1234 +
+ Resource by chained parameter value: http://fhir.example.com/Patient?provider:Organization.name=FooOrg +

+ +
+

diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDtTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDtTest.java new file mode 100644 index 00000000000..be7876c3614 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDtTest.java @@ -0,0 +1,100 @@ +package ca.uhn.fhir.model.dstu.composite; + +import static org.junit.Assert.*; + +import org.junit.BeforeClass; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.dstu.resource.Organization; +import ca.uhn.fhir.model.dstu.resource.Patient; + +public class ResourceReferenceDtTest { + + private static FhirContext ourCtx; + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceReferenceDtTest.class); + + @Test + public void testParseValueAbsolute() { + Patient patient = new Patient(); + ResourceReferenceDt rr = new ResourceReferenceDt(); + rr.setReference("http://foo/fhir/Organization/123"); + patient.setManagingOrganization(rr); + + Patient actual = parseAndEncode(patient); + rr = actual.getManagingOrganization(); + assertEquals(Organization.class, rr.getResourceType()); + assertEquals("123", rr.getResourceId()); + + } + + @Test + public void testParseValueMissingType1() { + Patient patient = new Patient(); + ResourceReferenceDt rr = new ResourceReferenceDt(); + rr.setReference("/123"); + patient.setManagingOrganization(rr); + + Patient actual = parseAndEncode(patient); + rr = actual.getManagingOrganization(); + assertEquals(null, rr.getResourceType()); + assertEquals("123", rr.getResourceId()); + + } + + @Test + public void testParseValueMissingType2() { + Patient patient = new Patient(); + ResourceReferenceDt rr = new ResourceReferenceDt(); + rr.setReference("123"); + patient.setManagingOrganization(rr); + + Patient actual = parseAndEncode(patient); + rr = actual.getManagingOrganization(); + assertEquals(null, rr.getResourceType()); + assertEquals("123", rr.getResourceId()); + + } + + @Test + public void testParseValueRelative1() { + Patient patient = new Patient(); + ResourceReferenceDt rr = new ResourceReferenceDt(); + rr.setReference("Organization/123"); + patient.setManagingOrganization(rr); + + Patient actual = parseAndEncode(patient); + rr = actual.getManagingOrganization(); + assertEquals(Organization.class, rr.getResourceType()); + assertEquals("123", rr.getResourceId()); + + } + + @Test + public void testParseValueRelative2() { + Patient patient = new Patient(); + ResourceReferenceDt rr = new ResourceReferenceDt(); + rr.setReference("/Organization/123"); + patient.setManagingOrganization(rr); + + Patient actual = parseAndEncode(patient); + rr = actual.getManagingOrganization(); + assertEquals(Organization.class, rr.getResourceType()); + assertEquals("123", rr.getResourceId()); + + } + + private Patient parseAndEncode(Patient patient) { + String encoded = ourCtx.newXmlParser().encodeResourceToString(patient); + ourLog.info("\n" + encoded); + return ourCtx.newXmlParser().parseResource(Patient.class, encoded); + } + + @BeforeClass + public static void beforeClass() { + ourCtx = new FhirContext(); + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ReferenceClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ReferenceClientTest.java new file mode 100644 index 00000000000..9450bc3e0fd --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ReferenceClientTest.java @@ -0,0 +1,129 @@ +package ca.uhn.fhir.rest.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.List; + +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.dstu.resource.Conformance; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.Constants; + +public class ReferenceClientTest { + + private FhirContext ctx; + private HttpClient httpClient; + private HttpResponse httpResponse; + + // atom-document-large.xml + + @Before + public void before() { + ctx = new FhirContext(Patient.class, Conformance.class); + + httpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ctx.getRestfulClientFactory().setHttpClient(httpClient); + + httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + } + + @Test + public void testWithParam() throws Exception { + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8"))); + + IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); + client.search(new ReferenceParam("123")); + + assertEquals(HttpGet.class, capt.getValue().getClass()); + HttpGet get = (HttpGet) capt.getValue(); + assertEquals("http://foo/Patient?provider=123", get.getURI().toString()); + } + + @Test + public void testWithParamAndChain() throws Exception { + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8"))); + + IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); + client.search(new ReferenceParam("chain", "123")); + + assertEquals(HttpGet.class, capt.getValue().getClass()); + HttpGet get = (HttpGet) capt.getValue(); + assertEquals("http://foo/Patient?provider.chain=123", get.getURI().toString()); + } + + @Test + public void testWithParamAndTypeAndChain() throws Exception { + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8"))); + + IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); + client.search(new ReferenceParam("Organization", "chain", "123")); + + assertEquals(HttpGet.class, capt.getValue().getClass()); + HttpGet get = (HttpGet) capt.getValue(); + assertEquals("http://foo/Patient?provider%3AOrganization.chain=123", get.getURI().toString()); + } + + @Test + public void testWithParamAndType() throws Exception { + 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_ATOM_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createBundle()), Charset.forName("UTF-8"))); + + IClient client = ctx.newRestfulClient(IClient.class, "http://foo"); + client.search(new ReferenceParam("Organization", null, "123")); + + assertEquals(HttpGet.class, capt.getValue().getClass()); + HttpGet get = (HttpGet) capt.getValue(); + assertEquals("http://foo/Patient?provider%3AOrganization=123", get.getURI().toString()); + } + + private String createBundle() { + return ctx.newXmlParser().encodeBundleToString(new Bundle()); + } + + + private interface IClient extends IBasicClient { + + @Search(type=Patient.class) + public List search(@RequiredParam(name=Patient.SP_PROVIDER) ReferenceParam theString); + + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java new file mode 100644 index 00000000000..ae3724d1234 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ReferenceParameterTest.java @@ -0,0 +1,165 @@ +package ca.uhn.fhir.rest.server; + +import static org.apache.commons.lang3.StringUtils.*; +import static org.junit.Assert.*; + +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.Patient; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.testutil.RandomServerPortProvider; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class ReferenceParameterTest { + + private static CloseableHttpClient ourClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReferenceParameterTest.class); + private static int ourPort; + private static Server ourServer; + private static FhirContext ourCtx; + + + + + @Test + public void testSearchWithValue() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Patient.SP_PROVIDER+"=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = new FhirContext().newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Patient p = (Patient) entries.get(0).getResource(); + assertEquals("0123",p.getName().get(0).getFamilyFirstRep().getValue()); + assertEquals("1",p.getName().get(1).getFamilyFirstRep().getValue()); + assertEquals("2",p.getName().get(2).getFamilyFirstRep().getValue()); + } + } + + @Test + public void testSearchWithValueAndType() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Patient.SP_PROVIDER+":Organization=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = new FhirContext().newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Patient p = (Patient) entries.get(0).getResource(); + assertEquals("0123",p.getName().get(0).getFamilyFirstRep().getValue()); + assertEquals("1Organization",p.getName().get(1).getFamilyFirstRep().getValue()); + assertEquals("2",p.getName().get(2).getFamilyFirstRep().getValue()); + } + } + @Test + public void testSearchWithValueAndTypeAndChain() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Patient.SP_PROVIDER+":Organization.name=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = new FhirContext().newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Patient p = (Patient) entries.get(0).getResource(); + assertEquals("0123",p.getName().get(0).getFamilyFirstRep().getValue()); + assertEquals("1Organization",p.getName().get(1).getFamilyFirstRep().getValue()); + assertEquals("2name",p.getName().get(2).getFamilyFirstRep().getValue()); + } + } + + @Test + public void testSearchWithValueAndChain() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?" + Patient.SP_PROVIDER+".name=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + List entries = new FhirContext().newXmlParser().parseBundle(responseContent).getEntries(); + assertEquals(1, entries.size()); + Patient p = (Patient) entries.get(0).getResource(); + assertEquals("0123",p.getName().get(0).getFamilyFirstRep().getValue()); + assertEquals("1",p.getName().get(1).getFamilyFirstRep().getValue()); + assertEquals("2name",p.getName().get(2).getFamilyFirstRep().getValue()); + } + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(ourPort); + + DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + 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 DummyPatientResourceProvider implements IResourceProvider { + + @Search + public List findPatient(@RequiredParam(name = Patient.SP_PROVIDER) ReferenceParam theParam) { + ArrayList retVal = new ArrayList(); + + Patient p = new Patient(); + p.addName().addFamily("0"+theParam.getValueAsQueryToken()); + p.addName().addFamily("1"+defaultString(theParam.getResourceType())); + p.addName().addFamily("2"+defaultString(theParam.getChain())); + retVal.add(p); + + return retVal; + } + + + + + @Override + public Class getResourceType() { + return Patient.class; + } + + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java index db918872d6c..9d7a7c31592 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java @@ -96,7 +96,9 @@ public class ResourceMethodTest { inputParams.add("lastName"); inputParams.add("mrn"); - assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True + Request params = Request.withResourceAndParams("Patient", RequestType.GET, inputParams); + boolean actual = rm.incomingServerRequestMatchesMethod(params); + assertTrue( actual); // True } @Test diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderTest.java similarity index 56% rename from hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java rename to hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderTest.java index 877a1c41c9a..8f45124b7d8 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DocumentationTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderTest.java @@ -13,6 +13,8 @@ import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.method.BaseMethodBinding; @@ -20,9 +22,9 @@ import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.param.SearchParameter; import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider; -public class DocumentationTest { +public class ServerConformanceProviderTest { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DocumentationTest.class); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderTest.class); @Test public void testSearchParameterDocumentation() throws Exception { @@ -55,6 +57,39 @@ public class DocumentationTest { assertThat(conf, containsString("")); } + + @Test + public void testMultiOptionalDocumentation() throws Exception { + + RestfulServer rs = new RestfulServer(); + rs.setProviders(new MultiOptionalProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(null); + + boolean found=false; + Collection resourceBindings = rs.getResourceBindings(); + for (ResourceBinding resourceBinding : resourceBindings) { + if (resourceBinding.getResourceName().equals("Patient")) { + List> methodBindings = resourceBinding.getMethodBindings(); + SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0); + SearchParameter param = (SearchParameter) binding.getParameters().iterator().next(); + assertEquals("The patient's identifier", param.getDescription()); + found=true; + } + } + assertTrue(found); + Conformance conformance = sc.getServerConformance(); + String conf = new FhirContext().newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + assertThat(conf, containsString("")); + assertThat(conf, containsString("")); + } + + /** * Created by dsotnikov on 2/25/2014. */ @@ -69,5 +104,23 @@ public class DocumentationTest { } } + + + /** + * Created by dsotnikov on 2/25/2014. + */ + @SuppressWarnings("unused") + public static class MultiOptionalProvider { + + @Search(type = Patient.class) + public Patient findPatient( + @Description(shortDefinition = "The patient's identifier") + @OptionalParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier, + @Description(shortDefinition = "The patient's name") + @OptionalParam(name=Patient.SP_NAME) StringDt theName) { + return null; + } + + } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SortTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SortTest.java new file mode 100644 index 00000000000..73194d27d50 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SortTest.java @@ -0,0 +1,177 @@ +package ca.uhn.fhir.rest.server; + +import static org.junit.Assert.*; + +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.Bundle; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Sort; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.testutil.RandomServerPortProvider; + +/** + * Created by dsotnikov on 2/25/2014. + */ +public class SortTest { + + private static CloseableHttpClient ourClient; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SortTest.class); + private static int ourPort; + private static Server ourServer; + + @Test + public void testNoSort() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?name=Hello"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals(1, p.getName().size()); + assertEquals("Hello", p.getNameFirstRep().getFamilyFirstRep().getValue()); + } + + @Test + public void testSingleSort() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?name=Hello&_sort=given"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals(2, p.getName().size()); + assertEquals("Hello", p.getNameFirstRep().getFamilyFirstRep().getValue()); + assertEquals("given|null", p.getName().get(1).getFamilyFirstRep().getValue()); + } + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?name=Hello&_sort:asc=given"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals(2, p.getName().size()); + assertEquals("Hello", p.getNameFirstRep().getFamilyFirstRep().getValue()); + assertEquals("given|ASC", p.getName().get(1).getFamilyFirstRep().getValue()); + } + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?name=Hello&_sort:desc=given"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals(2, p.getName().size()); + assertEquals("Hello", p.getNameFirstRep().getFamilyFirstRep().getValue()); + assertEquals("given|DESC", p.getName().get(1).getFamilyFirstRep().getValue()); + } + + } + + @Test + public void testSortChain() throws Exception { + { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?name=Hello&_sort=given&_sort=family&_sort=name"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals(4, p.getName().size()); + assertEquals("Hello", p.getNameFirstRep().getFamilyFirstRep().getValue()); + assertEquals("given|null", p.getName().get(1).getFamilyFirstRep().getValue()); + assertEquals("family|null", p.getName().get(2).getFamilyFirstRep().getValue()); + assertEquals("name|null", p.getName().get(3).getFamilyFirstRep().getValue()); + } + + + } + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + } + + @BeforeClass + public static void beforeClass() throws Exception { + ourPort = RandomServerPortProvider.findFreePort(); + ourServer = new Server(ourPort); + + DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + 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(); + + } + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class DummyPatientResourceProvider implements IResourceProvider { + + @Search + public List findPatient(@RequiredParam(name = Patient.SP_NAME) StringDt theName, @Sort SortSpec theSort) { + ArrayList retVal = new ArrayList(); + + Patient p = new Patient(); + p.addName().addFamily().setValue(theName.getValue()); + SortSpec sort = theSort; + while (sort != null) { + p.addName().addFamily().setValue(sort.getFieldName() + "|" + sort.getOrder()); + sort = sort.getChain(); + } + + retVal.add(p); + + return retVal; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + } + +} diff --git a/hapi-fhir-jpaserver-base/.classpath b/hapi-fhir-jpaserver-base/.classpath index 3e436a199d8..15f6337d7f2 100644 --- a/hapi-fhir-jpaserver-base/.classpath +++ b/hapi-fhir-jpaserver-base/.classpath @@ -1,151 +1,152 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-base/.gitignore b/hapi-fhir-jpaserver-base/.gitignore index 2f7896d1d13..548aaf5e303 100644 --- a/hapi-fhir-jpaserver-base/.gitignore +++ b/hapi-fhir-jpaserver-base/.gitignore @@ -1 +1,2 @@ target/ +/bin diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index cef4a355993..0801ca5194f 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -175,6 +175,59 @@ true + + + de.juplo + hibernate4-maven-plugin + 1.0.1 + + true + SCRIPT + false + + + + o10g + + export + + + org.hibernate.dialect.Oracle10gDialect + ${project.build.directory}/schema_oracle_10g.sql + + + + derby + + export + + + org.hibernate.dialect.DerbyTenSevenDialect + ${project.build.directory}/schema_derby.sql + + + + hsql + + export + + + org.hibernate.dialect.HSQLDialect + ${project.build.directory}/schema_hsql.sql + + + + mysql5 + + export + + + org.hibernate.dialect.MySQL5Dialect + ${project.build.directory}/schema_mysql_5.sql + + + + 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 c8f55acfaf0..8a3c526d72d 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 @@ -56,8 +56,10 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.dstu.composite.AddressDt; import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu.composite.CodingDt; +import ca.uhn.fhir.model.dstu.composite.ContactDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; @@ -96,6 +98,312 @@ public class FhirResourceDao private Class myTableType; + @Transactional(propagation = Propagation.REQUIRED, readOnly = true) + @Override + public MethodOutcome create(T theResource) { + + final X entity = toEntity(theResource); + + entity.setPublished(new Date()); + entity.setUpdated(entity.getPublished()); + + final List stringParams = extractSearchParamStrings(entity, theResource); + final List tokenParams = extractSearchParamTokens(entity, theResource); + final List numberParams = extractSearchParamNumber(entity, theResource); + final List dateParams = extractSearchParamDates(entity, theResource); + final List links = extractResourceLinks(entity, theResource); + + ourLog.info("Saving links: {}", links); + + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); + template.setReadOnly(false); + template.execute(new TransactionCallback() { + @Override + public X doInTransaction(TransactionStatus theStatus) { + myEntityManager.persist(entity); + for (ResourceIndexedSearchParamString next : stringParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamToken next : tokenParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamNumber next : numberParams) { + myEntityManager.persist(next); + } + for (ResourceIndexedSearchParamDate next : dateParams) { + myEntityManager.persist(next); + } + for (ResourceLink next : links) { + myEntityManager.persist(next); + } + return entity; + } + }); + + MethodOutcome outcome = toMethodOutcome(entity); + return outcome; + } + + public Class getResourceType() { + return myResourceType; + } + + @Override + public Class getTableType() { + return myTableType; + } + + @Transactional(propagation = Propagation.REQUIRED) + @Override + public List history(IdDt theId) { + ArrayList retVal = new ArrayList(); + + String resourceType = myCtx.getResourceDefinition(myResourceType).getName(); + TypedQuery q = myEntityManager.createQuery(ResourceHistoryTable.Q_GETALL, ResourceHistoryTable.class); + q.setParameter("PID", theId.asLong()); + q.setParameter("RESTYPE", resourceType); + + // TypedQuery query = + // myEntityManager.createQuery(criteriaQuery); + List results = q.getResultList(); + for (ResourceHistoryTable next : results) { + retVal.add(toResource(next)); + } + + try { + retVal.add(read(theId)); + } catch (ResourceNotFoundException e) { + // ignore + } + + if (retVal.isEmpty()) { + throw new ResourceNotFoundException(theId); + } + + return retVal; + } + + @PostConstruct + public void postConstruct() throws Exception { + myResourceType = myTableType.newInstance().getResourceType(); + myCtx = new FhirContext(myResourceType); + myResourceName = myCtx.getResourceDefinition(myResourceType).getName(); + } + + @Transactional(propagation = Propagation.REQUIRED) + @Override + public T read(IdDt theId) { + X entity = readEntity(theId); + + T retVal = toResource(entity); + return retVal; + } + + @Override + public List search(Map theParams) { + SearchParameterMap map = new SearchParameterMap(); + for (Entry nextEntry : theParams.entrySet()) { + map.put(nextEntry.getKey(), new ArrayList>()); + map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); + } + return search(map); + } + + @Override + public List search(SearchParameterMap theParams) { + + Set pids; + if (theParams.isEmpty()) { + pids = null; + } else { + pids = searchForIdsWithAndOr(theParams); + if (pids.isEmpty()) { + return new ArrayList<>(); + } + } + + // Execute the query and make sure we return distinct results + { + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(myTableType); + Root from = cq.from(myTableType); + if (!theParams.isEmpty()) { + cq.where(from.get("myId").in(pids)); + } + TypedQuery q = myEntityManager.createQuery(cq); + + List retVal = new ArrayList<>(); + for (X next : q.getResultList()) { + T resource = toResource(next); + retVal.add(resource); + } + return retVal; + } + } + + @Override + public List search(String theParameterName, IQueryParameterType theValue) { + return search(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIds(Map theParams) { + Map>> map = new HashMap>>(); + for (Entry nextEntry : theParams.entrySet()) { + map.put(nextEntry.getKey(), new ArrayList>()); + map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); + } + return searchForIdsWithAndOr(map); + } + + @Override + public Set searchForIds(String theParameterName, IQueryParameterType theValue) { + return searchForIds(Collections.singletonMap(theParameterName, theValue)); + } + + @Override + public Set searchForIdsWithAndOr(Map>> theParams) { + Map>> params = theParams; + if (params == null) { + params = Collections.emptyMap(); + } + + RuntimeResourceDefinition resourceDef = myCtx.getResourceDefinition(myResourceType); + + Set pids = new HashSet(); + + for (Entry>> nextParamEntry : params.entrySet()) { + String nextParamName = nextParamEntry.getKey(); + RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); + if (nextParamDef != null) { + if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateToken(pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateString(pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateQuantity(pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateDate(pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + } else if (nextParamDef.getParamType() == SearchParamTypeEnum.REFERENCE) { + for (List nextAnd : nextParamEntry.getValue()) { + pids = addPredicateReference(nextParamName, pids, nextAnd); + if (pids.isEmpty()) { + return new HashSet(); + } + } + } else { + throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType()); + } + } + } + + return pids; + } + + @Required + public void setTableType(Class theTableType) { + myTableType = theTableType; + } + + @Transactional(propagation = Propagation.SUPPORTS) + @Override + public MethodOutcome update(final T theResource, final IdDt theId) { + + TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); + X savedEntity = template.execute(new TransactionCallback() { + @Override + public X doInTransaction(TransactionStatus theStatus) { + + final X entity = readEntity(theId); + entity.setUpdated(entity.getPublished()); + + final ResourceHistoryTable historyEntry = entity.toHistory(myCtx); + + final List stringParams = extractSearchParamStrings(entity, theResource); + final List tokenParams = extractSearchParamTokens(entity, theResource); + final List numberParams = extractSearchParamNumber(entity, theResource); + final List dateParams = extractSearchParamDates(entity, theResource); + final List links = extractResourceLinks(entity, theResource); + + populateResourceIntoEntity(theResource, entity); + myEntityManager.persist(historyEntry); + + entity.setUpdated(new Date()); + myEntityManager.persist(entity); + + if (entity.isParamsStringPopulated()) { + for (ResourceIndexedSearchParamString next : entity.getParamsString()) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamString next : stringParams) { + myEntityManager.persist(next); + } + + if (entity.isParamsTokenPopulated()) { + for (ResourceIndexedSearchParamToken next : entity.getParamsToken()) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamToken next : tokenParams) { + myEntityManager.persist(next); + } + + if (entity.isParamsNumberPopulated()) { + for (ResourceIndexedSearchParamNumber next : entity.getParamsNumber()) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamNumber next : numberParams) { + myEntityManager.persist(next); + } + + if (entity.isParamsDatePopulated()) { + for (ResourceIndexedSearchParamDate next : entity.getParamsDate()) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamDate next : dateParams) { + myEntityManager.persist(next); + } + + if (entity.isHasLinks()) { + for (ResourceLink next : entity.getResourceLinks()) { + myEntityManager.remove(next); + } + } + for (ResourceLink next : links) { + myEntityManager.persist(next); + } + + return entity; + } + }); + + return toMethodOutcome(savedEntity); + } + private Set addPredicateDate(Set thePids, List theOrParams) { if (theOrParams == null || theOrParams.isEmpty()) { return thePids; @@ -266,19 +574,19 @@ public class FhirResourceDao Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid); codePredicates.add(eq); } else { - // TODO: handle chain with resource type String chain = myCtx.getResourceDefinition(myResourceType).getSearchParam(theParamName).getPath(); BaseRuntimeChildDefinition def = myCtx.newTerser().getDefinition(myResourceType, chain); if (!(def instanceof RuntimeChildResourceDefinition)) { throw new ConfigurationException("Property " + chain + " of type " + myResourceName + " is not a resource: " + def.getClass()); } List> resourceTypes; - if (ref.getType() == null) { + if (isBlank(ref.getResourceType())) { RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def; resourceTypes = resDef.getResourceTypes(); } else { resourceTypes = new ArrayList<>(); - resourceTypes.add(ref.getType()); + RuntimeResourceDefinition resDef = myCtx.getResourceDefinition(ref.getResourceType()); + resourceTypes.add(resDef.getImplementingClass()); } for (Class nextType : resourceTypes) { RuntimeResourceDefinition typeDef = myCtx.getResourceDefinition(nextType); @@ -313,7 +621,10 @@ public class FhirResourceDao Predicate masterCodePredicate = builder.or(codePredicates.toArray(new Predicate[0])); - Predicate type = builder.equal(from.get("mySourcePath"), myResourceName + "." + theParamName); + RuntimeSearchParam param = myCtx.getResourceDefinition(getResourceType()).getSearchParam(theParamName); + String path = param.getPath(); + + Predicate type = builder.equal(from.get("mySourcePath"), path); if (pidsToRetain.size() > 0) { Predicate inPids = (from.get("mySourceResourcePid").in(pidsToRetain)); cq.where(builder.and(type, inPids, masterCodePredicate)); @@ -422,53 +733,6 @@ public class FhirResourceDao return new HashSet(q.getResultList()); } - @Transactional(propagation = Propagation.REQUIRED, readOnly = true) - @Override - public MethodOutcome create(T theResource) { - - final X entity = toEntity(theResource); - - entity.setPublished(new Date()); - entity.setUpdated(entity.getPublished()); - - final List stringParams = extractSearchParamStrings(entity, theResource); - final List tokenParams = extractSearchParamTokens(entity, theResource); - final List numberParams = extractSearchParamNumber(entity, theResource); - final List dateParams = extractSearchParamDates(entity, theResource); - final List links = extractResourceLinks(entity, theResource); - - ourLog.info("Saving links: {}", links); - - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); - template.setReadOnly(false); - template.execute(new TransactionCallback() { - @Override - public X doInTransaction(TransactionStatus theStatus) { - myEntityManager.persist(entity); - for (ResourceIndexedSearchParamString next : stringParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamToken next : tokenParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamNumber next : numberParams) { - myEntityManager.persist(next); - } - for (ResourceIndexedSearchParamDate next : dateParams) { - myEntityManager.persist(next); - } - for (ResourceLink next : links) { - myEntityManager.persist(next); - } - return entity; - } - }); - - MethodOutcome outcome = toMethodOutcome(entity); - return outcome; - } - private List extractResourceLinks(X theEntity, T theResource) { ArrayList retVal = new ArrayList(); @@ -533,6 +797,8 @@ public class FhirResourceDao } } + theEntity.setHasLinks(retVal.size() > 0); + return retVal; } @@ -580,6 +846,8 @@ public class FhirResourceDao } } + theEntity.setParamsDatePopulated(retVal.size() > 0); + return retVal; } @@ -608,8 +876,7 @@ public class FhirResourceDao if (nextObject instanceof QuantityDt) { QuantityDt nextValue = (QuantityDt) nextObject; - ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), - nextValue.getUnits().getValue()); + ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), nextValue.getUnits().getValue()); nextEntity.setResource(theEntity, def.getName()); retVal.add(nextEntity); } else { @@ -622,6 +889,8 @@ public class FhirResourceDao } } + theEntity.setParamsNumberPopulated(retVal.size() > 0); + return retVal; } @@ -657,42 +926,57 @@ public class FhirResourceDao ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextValue.getValueAsString()), nextValue.getValueAsString()); nextEntity.setResource(theEntity, def.getName()); retVal.add(nextEntity); - } else if (nextObject instanceof HumanNameDt) { - ArrayList allNames = new ArrayList<>(); - allNames.addAll(((HumanNameDt) nextObject).getFamily()); - allNames.addAll(((HumanNameDt) nextObject).getGiven()); - for (StringDt nextName : allNames) { - if (nextName.isEmpty()) { - continue; - } - ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextName.getValueAsString()), nextName.getValueAsString()); - nextEntity.setResource(theEntity, def.getName()); - retVal.add(nextEntity); - } } else { - if (!multiType) { - throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); + if (nextObject instanceof HumanNameDt) { + ArrayList allNames = new ArrayList<>(); + HumanNameDt nextHumanName = (HumanNameDt) nextObject; + allNames.addAll(nextHumanName.getFamily()); + allNames.addAll(nextHumanName.getGiven()); + for (StringDt nextName : allNames) { + if (nextName.isEmpty()) { + continue; + } + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextName.getValueAsString()), nextName.getValueAsString()); + nextEntity.setResource(theEntity, def.getName()); + retVal.add(nextEntity); + } + } else if (nextObject instanceof AddressDt) { + ArrayList allNames = new ArrayList<>(); + AddressDt nextAddress = (AddressDt) nextObject; + allNames.addAll(nextAddress.getLine()); + allNames.add(nextAddress.getCity()); + allNames.add(nextAddress.getState()); + allNames.add(nextAddress.getCountry()); + allNames.add(nextAddress.getZip()); + for (StringDt nextName : allNames) { + if (nextName.isEmpty()) { + continue; + } + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextName.getValueAsString()), nextName.getValueAsString()); + nextEntity.setResource(theEntity, def.getName()); + retVal.add(nextEntity); + } + } else if (nextObject instanceof ContactDt) { + ContactDt nextContact = (ContactDt) nextObject; + if (nextContact.getValue().isEmpty() == false) { + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextContact.getValue().getValueAsString()), nextContact.getValue().getValueAsString()); + nextEntity.setResource(theEntity, def.getName()); + retVal.add(nextEntity); + } + } else { + if (!multiType) { + throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass()); + } } } } } + + theEntity.setParamsStringPopulated(retVal.size() > 0); return retVal; } - private String normalizeString(String theString) { - char[] out = new char[theString.length()]; - theString = Normalizer.normalize(theString, Normalizer.Form.NFD); - int j = 0; - for (int i = 0, n = theString.length(); i < n; ++i) { - char c = theString.charAt(i); - if (c <= '\u007F') { - out[j++] = c; - } - } - return new String(out).toUpperCase(); - } - private List extractSearchParamTokens(X theEntity, T theResource) { ArrayList retVal = new ArrayList(); @@ -753,11 +1037,9 @@ public class FhirResourceDao } } - return retVal; - } + theEntity.setParamsTokenPopulated(retVal.size() > 0); - public Class getResourceType() { - return myResourceType; + return retVal; } private Map, IFhirResourceDao> getResourceTypeToDao() { @@ -772,39 +1054,17 @@ public class FhirResourceDao return resourceTypeToDao; } - @Override - public Class getTableType() { - return myTableType; - } - - @Transactional(propagation = Propagation.REQUIRED) - @Override - public List history(IdDt theId) { - ArrayList retVal = new ArrayList(); - - String resourceType = myCtx.getResourceDefinition(myResourceType).getName(); - TypedQuery q = myEntityManager.createQuery(ResourceHistoryTable.Q_GETALL, ResourceHistoryTable.class); - q.setParameter("PID", theId.asLong()); - q.setParameter("RESTYPE", resourceType); - - // TypedQuery query = - // myEntityManager.createQuery(criteriaQuery); - List results = q.getResultList(); - for (ResourceHistoryTable next : results) { - retVal.add(toResource(next)); + private String normalizeString(String theString) { + char[] out = new char[theString.length()]; + theString = Normalizer.normalize(theString, Normalizer.Form.NFD); + int j = 0; + for (int i = 0, n = theString.length(); i < n; ++i) { + char c = theString.charAt(i); + if (c <= '\u007F') { + out[j++] = c; + } } - - try { - retVal.add(read(theId)); - } catch (ResourceNotFoundException e) { - // ignore - } - - if (retVal.isEmpty()) { - throw new ResourceNotFoundException(theId); - } - - return retVal; + return new String(out).toUpperCase(); } private void populateResourceIntoEntity(T theResource, X retVal) { @@ -820,22 +1080,6 @@ public class FhirResourceDao } - @PostConstruct - public void postConstruct() throws Exception { - myResourceType = myTableType.newInstance().getResourceType(); - myCtx = new FhirContext(myResourceType); - myResourceName = myCtx.getResourceDefinition(myResourceType).getName(); - } - - @Transactional(propagation = Propagation.REQUIRED) - @Override - public T read(IdDt theId) { - X entity = readEntity(theId); - - T retVal = toResource(entity); - return retVal; - } - private X readEntity(IdDt theId) { X entity = (X) myEntityManager.find(myTableType, theId.asLong()); if (entity == null) { @@ -844,132 +1088,6 @@ public class FhirResourceDao return entity; } - @Override - public List search(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.put(nextEntry.getKey(), new ArrayList>()); - map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); - } - return search(map); - } - - @Override - public List search(String theParameterName, IQueryParameterType theValue) { - return search(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public Set searchForIds(Map theParams) { - Map>> map = new HashMap>>(); - for (Entry nextEntry : theParams.entrySet()) { - map.put(nextEntry.getKey(), new ArrayList>()); - map.get(nextEntry.getKey()).add(Collections.singletonList(nextEntry.getValue())); - } - return searchForIdsWithAndOr(map); - } - - @Override - public Set searchForIdsWithAndOr(Map>> theParams) { - Map>> params = theParams; - if (params == null) { - params = Collections.emptyMap(); - } - - RuntimeResourceDefinition resourceDef = myCtx.getResourceDefinition(myResourceType); - - Set pids = new HashSet(); - - for (Entry>> nextParamEntry : params.entrySet()) { - String nextParamName = nextParamEntry.getKey(); - RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); - if (nextParamDef != null) { - if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateToken(pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateString(pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateQuantity(pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateDate(pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - } else if (nextParamDef.getParamType() == SearchParamTypeEnum.REFERENCE) { - for (List nextAnd : nextParamEntry.getValue()) { - pids = addPredicateReference(nextParamName, pids, nextAnd); - if (pids.isEmpty()) { - return new HashSet(); - } - } - } else { - throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType()); - } - } - } - - return pids; - } - - @Override - public Set searchForIds(String theParameterName, IQueryParameterType theValue) { - return searchForIds(Collections.singletonMap(theParameterName, theValue)); - } - - @Override - public List search(SearchParameterMap theParams) { - - Set pids; - if (theParams.isEmpty()) { - pids = null; - } else { - pids = searchForIdsWithAndOr(theParams); - if (pids.isEmpty()) { - return new ArrayList<>(); - } - } - - // Execute the query and make sure we return distinct results - { - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(myTableType); - Root from = cq.from(myTableType); - if (!theParams.isEmpty()) { - cq.where(from.get("myId").in(pids)); - } - TypedQuery q = myEntityManager.createQuery(cq); - - List retVal = new ArrayList<>(); - for (X next : q.getResultList()) { - T resource = toResource(next); - retVal.add(resource); - } - return retVal; - } - } - - @Required - public void setTableType(Class theTableType) { - myTableType = theTableType; - } - private X toEntity(T theResource) { X retVal; try { @@ -1034,27 +1152,4 @@ public class FhirResourceDao } return retVal; } - - @Transactional(propagation = Propagation.SUPPORTS) - @Override - public MethodOutcome update(final T theResource, final IdDt theId) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - X savedEntity = template.execute(new TransactionCallback() { - @Override - public X doInTransaction(TransactionStatus theStatus) { - final X entity = readEntity(theId); - final ResourceHistoryTable existing = entity.toHistory(myCtx); - - populateResourceIntoEntity(theResource, entity); - myEntityManager.persist(existing); - - entity.setUpdated(new Date()); - myEntityManager.persist(entity); - return entity; - } - }); - - 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 3a8ce260a7c..64ea3207db4 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 @@ -5,15 +5,17 @@ import java.util.HashMap; import java.util.List; 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.method.QualifiedParamList; -import ca.uhn.fhir.rest.param.DateRangeParam; public class SearchParameterMap extends HashMap>> { private static final long serialVersionUID = 1L; public void add(String theName, IQueryParameterType theParam) { + if (theParam == null) { + return; + } if (!containsKey(theName)) { put(theName, new ArrayList>()); } @@ -22,13 +24,19 @@ public class SearchParameterMap extends HashMap>()); } - for (QualifiedParamList next : theBirthdate.getValuesAsQueryTokens()) { - next.get + for (IQueryParameterOr next : theAnd.getValuesAsQueryTokens()) { + if (next==null) { + continue; + } + get(theName).add(next.getValuesAsQueryTokens()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseResourceTable.java index a97e936f008..abcdf594f0c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseResourceTable.java @@ -28,17 +28,29 @@ import ca.uhn.fhir.model.primitive.IdDt; @DiscriminatorColumn(name = "SVCVER_TYPE", length = 20, discriminatorType = DiscriminatorType.STRING) public abstract class BaseResourceTable extends BaseHasResource { + @Column(name = "SP_HAS_LINKS") + private boolean myHasLinks; + @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "RES_ID") private Long myId; + @OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) + private Collection myIncomingResourceLinks; + @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection myParamsDate; @Column(name = "SP_DATE_PRESENT") private boolean myParamsDatePopulated; + @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) + private Collection myParamsNumber; + + @Column(name = "SP_NUMBER_PRESENT") + private boolean myParamsNumberPopulated; + @OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) private Collection myParamsString; @@ -51,6 +63,9 @@ public abstract class BaseResourceTable extends BaseHasReso @Column(name = "SP_TOKEN_PRESENT") private boolean myParamsTokenPopulated; + @OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false) + private Collection myResourceLinks; + @OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) private Collection myTags; @@ -67,14 +82,14 @@ public abstract class BaseResourceTable extends BaseHasReso getTags().add(new ResourceTag(this, theTerm, theLabel, theScheme)); } - public Long getIdAsLong() { - return myId; - } - public IdDt getId() { return new IdDt(myId); } + public Long getIdAsLong() { + return myId; + } + public Collection getParamsDate() { if (myParamsDate == null) { myParamsDate = new ArrayList<>(); @@ -82,6 +97,13 @@ public abstract class BaseResourceTable extends BaseHasReso return myParamsDate; } + public Collection getParamsNumber() { + if (myParamsNumber == null) { + myParamsNumber = new ArrayList<>(); + } + return myParamsNumber; + } + public Collection getParamsString() { if (myParamsString == null) { myParamsString = new ArrayList<>(); @@ -96,6 +118,13 @@ public abstract class BaseResourceTable extends BaseHasReso return myParamsToken; } + public Collection getResourceLinks() { + if (myResourceLinks == null) { + myResourceLinks = new ArrayList<>(); + } + return myResourceLinks; + } + public abstract Class getResourceType(); public Collection getTags() { @@ -109,10 +138,18 @@ public abstract class BaseResourceTable extends BaseHasReso return new IdDt(myVersion); } + public boolean isHasLinks() { + return myHasLinks; + } + public boolean isParamsDatePopulated() { return myParamsDatePopulated; } + public boolean isParamsNumberPopulated() { + return myParamsNumberPopulated; + } + public boolean isParamsStringPopulated() { return myParamsStringPopulated; } @@ -121,6 +158,10 @@ public abstract class BaseResourceTable extends BaseHasReso return myParamsTokenPopulated; } + public void setHasLinks(boolean theHasLinks) { + myHasLinks = theHasLinks; + } + public void setId(IdDt theId) { myId = theId.asLong(); } @@ -133,6 +174,10 @@ public abstract class BaseResourceTable extends BaseHasReso myParamsDatePopulated = theParamsDatePopulated; } + public void setParamsNumberPopulated(boolean theParamsNumberPopulated) { + myParamsNumberPopulated = theParamsNumberPopulated; + } + public void setParamsString(Collection theParamsString) { myParamsString = theParamsString; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java index 41ddadcaa8d..9d792c631b2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceHistoryTag.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.entity; import java.io.Serializable; import javax.persistence.Entity; +import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -26,7 +27,7 @@ public class ResourceHistoryTag extends BaseTag implements Serializable { @JoinColumn(name="RES_TYPE", referencedColumnName="RES_TYPE"), @JoinColumn(name="PID", referencedColumnName="PID"), @JoinColumn(name="VERSION", referencedColumnName="VERSION") - }) + }, foreignKey=@ForeignKey(name="FK_HT_RT")) private ResourceHistoryTable myResourceHistory; public ResourceHistoryTag() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java index e9f21801030..3741dfb978d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java @@ -4,6 +4,8 @@ import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @@ -11,7 +13,7 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity -@Table(name = "IDX_SP_DATE") +@Table(name = "SPIDX_DATE", indexes= {@Index(name="IDX_SP_DATE", columnList="myValueLow,myValueHigh")}) public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; @@ -25,7 +27,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar public Date myValueLow; @ManyToOne(optional = false) - @JoinColumn(name = "RESOURCE_PID", nullable = false) + @JoinColumn(name = "RESOURCE_PID", nullable = false, foreignKey=@ForeignKey(name="FK_ISD_RESOURCE")) private BaseResourceTable myResource; @Column(name = "RESOURCE_PID", insertable = false, updatable = false) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java index da501c48934..a961a306337 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java @@ -4,12 +4,14 @@ import java.math.BigDecimal; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity -@Table(name = "IDX_SP_NUMBER") +@Table(name = "SPIDX_NUMBER", indexes= {@Index(name="IDX_SP_NUMBER", columnList="myValue")}) public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; @@ -24,7 +26,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP public BigDecimal myValue; @ManyToOne(optional = false) - @JoinColumn(name = "RESOURCE_PID", nullable = false) + @JoinColumn(name = "RESOURCE_PID", nullable = false, foreignKey=@ForeignKey(name="FK_ISN_RESOURCE")) private BaseResourceTable myResource; @Column(name = "RESOURCE_PID", insertable = false, updatable = false) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java index 7fa8c48b6ad..a3e430db211 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java @@ -3,18 +3,20 @@ package ca.uhn.fhir.jpa.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; +import javax.persistence.ForeignKey; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity -@Table(name = "IDX_SP_STRING") +@Table(name = "SPIDX_STRING", indexes= {@Index(name="IDX_SP_STRING", columnList="myValueNormalized")}) public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; @ManyToOne(optional = false, cascade = {}, fetch = FetchType.LAZY) - @JoinColumn(name = "RESOURCE_PID", nullable = false) + @JoinColumn(name = "RESOURCE_PID", nullable = false, foreignKey=@ForeignKey(name="FK_ISS_RESOURCE")) private BaseResourceTable myResource; @Column(name = "RESOURCE_PID", insertable=false, updatable=false) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java index 753834a4be1..36112a1b1c7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java @@ -2,18 +2,20 @@ package ca.uhn.fhir.jpa.entity; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity -@Table(name = "IDX_SP_TOKEN") +@Table(name = "SPIDX_TOKEN", indexes= {@Index(name="IDX_SP_STRING", columnList="mySystem,myValue")}) public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam { private static final long serialVersionUID = 1L; @ManyToOne(optional = false) - @JoinColumn(name = "RESOURCE_PID", nullable = false) + @JoinColumn(name = "RESOURCE_PID", nullable = false, foreignKey=@ForeignKey(name="FK_IST_RESOURCE")) private BaseResourceTable myResource; @Column(name = "RESOURCE_PID", insertable=false, updatable=false) @@ -33,6 +35,9 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa mySystem = theSystem; } + public ResourceIndexedSearchParamToken() { + } + public ResourceIndexedSearchParamToken(String theName, String theSystem, String theValue) { setName(theName); setSystem(theSystem); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java index 4824f6b28bf..13a1e922e48 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.entity; import javax.persistence.Entity; +import javax.persistence.ForeignKey; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -19,7 +20,7 @@ public class ResourceTag extends BaseTag { private Long myId; @ManyToOne(cascade= {}) - @JoinColumn(name = "RESOURCE_PID", nullable=false) + @JoinColumn(name = "RESOURCE_PID", nullable=false, foreignKey=@ForeignKey(name="FK_RESTAG_RESOURCE")) private BaseResourceTable myResource; public ResourceTag() { 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 9b9cede8078..8384f35c7cc 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 @@ -1,18 +1,14 @@ package ca.uhn.fhir.jpa.dao; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +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; import java.util.Map; +import java.util.Set; import org.hamcrest.core.StringContains; import org.junit.AfterClass; @@ -190,7 +186,7 @@ public class FhirResourceDaoTest { found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "F")); assertEquals(0, found.size()); - Map>> map = new HashMap<>(); + SearchParameterMap map = new SearchParameterMap(); map.put(Patient.SP_IDENTIFIER, new ArrayList>()); map.get(Patient.SP_IDENTIFIER).add(new ArrayList()); map.get(Patient.SP_IDENTIFIER).get(0).add(new IdentifierDt("urn:system", "001testPersistSearchParams")); @@ -201,7 +197,7 @@ public class FhirResourceDaoTest { assertEquals(1, found.size()); assertEquals(id, found.get(0).getId().asLong().longValue()); - map = new HashMap<>(); + map = new SearchParameterMap(); map.put(Patient.SP_IDENTIFIER, new ArrayList>()); map.get(Patient.SP_IDENTIFIER).add(new ArrayList()); map.get(Patient.SP_IDENTIFIER).get(0).add(new IdentifierDt("urn:system", "001testPersistSearchParams")); @@ -358,7 +354,7 @@ public class FhirResourceDaoTest { result = ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypesYY")); assertEquals(0,result.size()); - result = ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam(Patient.class, Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")); + result = ourObservationDao.search(Observation.SP_SUBJECT, new ReferenceParam("Patient", Patient.SP_NAME, "testSearchResourceLinkWithChainWithMultipleTypes01")); assertEquals(1,result.size()); assertEquals(obsId01, result.get(0).getId()); @@ -505,6 +501,36 @@ public class FhirResourceDaoTest { } + + @Test + public void testUpdateMaintainsSearchParams() throws InterruptedException { + Patient p1 = new Patient(); + p1.addIdentifier("urn:system", "testUpdateMaintainsSearchParamsAAA"); + p1.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsAAA"); + IdDt p1id = ourPatientDao.create(p1).getId(); + + Patient p2 = new Patient(); + p2.addIdentifier("urn:system", "testUpdateMaintainsSearchParamsBBB"); + p2.addName().addFamily("Tester").addGiven("testUpdateMaintainsSearchParamsBBB"); + IdDt p2id = ourPatientDao.create(p2).getId(); + + Set ids = ourPatientDao.searchForIds(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsAAA")); + assertEquals(1,ids.size()); + assertThat(ids, contains(p1id.asLong())); + + // Update the name + p1.getNameFirstRep().getGivenFirstRep().setValue("testUpdateMaintainsSearchParamsBBB"); + ourPatientDao.update(p1, p1id); + + ids = ourPatientDao.searchForIds(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsAAA")); + assertEquals(0,ids.size()); + + ids = ourPatientDao.searchForIds(Patient.SP_GIVEN, new StringDt("testUpdateMaintainsSearchParamsBBB")); + assertEquals(2,ids.size()); + + } + + @AfterClass public static void afterClass() { ourCtx.close(); @@ -514,7 +540,7 @@ public class FhirResourceDaoTest { @BeforeClass public static void beforeClass() { ourTestStarted = new Date(); - ourCtx = new ClassPathXmlApplicationContext("fhir-spring-test-config.xml"); + ourCtx = new ClassPathXmlApplicationContext("fhir-jpabase-spring-test-config.xml"); ourPatientDao = ourCtx.getBean("myPatientDao", IFhirResourceDao.class); ourObservationDao = ourCtx.getBean("myObservationDao", IFhirResourceDao.class); ourDiagnosticReportDao = ourCtx.getBean("myDiagnosticReportDao", IFhirResourceDao.class); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DeviceResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DeviceResourceTable.java similarity index 78% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DeviceResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DeviceResourceTable.java index 18ca1827988..4e999da5f88 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DeviceResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DeviceResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Device; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DiagnosticReportResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DiagnosticReportResourceTable.java similarity index 81% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DiagnosticReportResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DiagnosticReportResourceTable.java index 2d661b1f142..b2eaa1a06d3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/DiagnosticReportResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/DiagnosticReportResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/LocationResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/LocationResourceTable.java similarity index 79% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/LocationResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/LocationResourceTable.java index 7b9cc3a1b8a..5bad4b1f238 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/LocationResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/LocationResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Location; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ObservationResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/ObservationResourceTable.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ObservationResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/ObservationResourceTable.java index 5b82911d292..edcafe8c641 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ObservationResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/ObservationResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Observation; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/OrganizationResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/OrganizationResourceTable.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/OrganizationResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/OrganizationResourceTable.java index 1d4c0493022..0dac1caf31d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/OrganizationResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/OrganizationResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Organization; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PatientResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/PatientResourceTable.java similarity index 79% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PatientResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/PatientResourceTable.java index 18de4f5a300..10b5dd0774c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/PatientResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/PatientResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Patient; @Entity diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/QuestionnaireResourceTable.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/QuestionnaireResourceTable.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/QuestionnaireResourceTable.java rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/QuestionnaireResourceTable.java index 5ee25fedac7..ca743e1dc1d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/QuestionnaireResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/testentity/QuestionnaireResourceTable.java @@ -1,8 +1,9 @@ -package ca.uhn.fhir.jpa.entity; +package ca.uhn.fhir.jpa.testentity; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import ca.uhn.fhir.jpa.entity.BaseResourceTable; import ca.uhn.fhir.model.dstu.resource.Questionnaire; @Entity diff --git a/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml new file mode 100644 index 00000000000..4b9f5e7cc2e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,50 @@ + + + + + org.hibernate.ejb.HibernatePersistence + + + ca.uhn.fhir.jpa.testentity.DeviceResourceTable + ca.uhn.fhir.jpa.testentity.DiagnosticReportResourceTable + ca.uhn.fhir.jpa.testentity.LocationResourceTable + ca.uhn.fhir.jpa.testentity.ObservationResourceTable + ca.uhn.fhir.jpa.testentity.OrganizationResourceTable + ca.uhn.fhir.jpa.testentity.PatientResourceTable + ca.uhn.fhir.jpa.testentity.QuestionnaireResourceTable + ca.uhn.fhir.jpa.testentity.QuestionnaireResourceTable + + ca.uhn.fhir.jpa.entity.ResourceHistoryTable + ca.uhn.fhir.jpa.entity.ResourceHistoryTag + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken + ca.uhn.fhir.jpa.entity.ResourceLink + ca.uhn.fhir.jpa.entity.ResourceTag + + false + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-jpaserver-base/src/test/resources/fhir-spring-test-config.xml b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml similarity index 85% rename from hapi-fhir-jpaserver-base/src/test/resources/fhir-spring-test-config.xml rename to hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml index 63010aca6ba..6a6c05309ab 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/fhir-spring-test-config.xml +++ b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -14,22 +14,22 @@ - + - + - + - + - + - + diff --git a/hapi-fhir-jpaserver-test/.classpath b/hapi-fhir-jpaserver-test/.classpath index 6bfebeceb5a..13d8e1e218a 100644 --- a/hapi-fhir-jpaserver-test/.classpath +++ b/hapi-fhir-jpaserver-test/.classpath @@ -1,6 +1,7 @@ + diff --git a/hapi-fhir-jpaserver-test/pom.xml b/hapi-fhir-jpaserver-test/pom.xml index 606f7a9d63b..e77d26d3904 100644 --- a/hapi-fhir-jpaserver-test/pom.xml +++ b/hapi-fhir-jpaserver-test/pom.xml @@ -80,6 +80,33 @@ + + ca.uhn.hapi.fhir + hapi-tinder-plugin + 0.3 + + + buildclient + + generate-jparest-server + + + ca.uhn.test.jpasrv + + device + location + observation + organization + patient + practitioner + questionnaire + valueset + + true + + + + org.apache.maven.plugins maven-deploy-plugin diff --git a/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java b/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java new file mode 100644 index 00000000000..66e0ed4be37 --- /dev/null +++ b/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java @@ -0,0 +1,177 @@ +package ca.uhn.fhir.jpa.test; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu.resource.Observation; +import ca.uhn.fhir.model.dstu.resource.Organization; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.dstu.resource.Questionnaire; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.tester.RestfulServerTesterServlet; +import ca.uhn.test.jpasrv.ObservationResourceProvider; +import ca.uhn.test.jpasrv.OrganizationResourceProvider; +import ca.uhn.test.jpasrv.PatientResourceProvider; + +public class CompleteResourceProviderTest { + + private static ClassPathXmlApplicationContext ourAppCtx; + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class); + + private static Server ourServer; + private static IFhirResourceDao patientDao; + private static IFhirResourceDao questionnaireDao; + private static IGenericClient ourClient; + private static IFhirResourceDao observationDao; + + // @Test + // public void test01UploadTestResources() throws Exception { + // + // IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8888/fhir/context"); + // + // File[] files = new File("src/test/resources/resources").listFiles(new PatternFilenameFilter(".*patient.*")); + // for (File file : files) { + // ourLog.info("Uploading: {}", file); + // Patient patient = ourCtx.newXmlParser().parseResource(Patient.class, new FileReader(file)); + // client.create(patient); + // } + // + // files = new File("src/test/resources/resources").listFiles(new PatternFilenameFilter(".*questionnaire.*")); + // for (File file : files) { + // ourLog.info("Uploading: {}", file); + // Questionnaire patient = ourCtx.newXmlParser().parseResource(Questionnaire.class, new FileReader(file)); + // client.create(patient); + // } + // + // } + + @Test + public void testSearchByIdentifier() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01"); + p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01"); + IdDt p1Id = ourClient.create(p1).getId(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02"); + p2.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02"); + ourClient.create(p2).getId(); + + Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")).encodedJson().prettyPrint().execute(); + assertEquals(1, actual.size()); + assertEquals(p1Id, actual.getEntries().get(0).getId()); + + } + + @Test + public void testSearchByResourceChain() { + Organization o1 = new Organization(); + o1.setName("testSearchByResourceChainName01"); + IdDt o1id = ourClient.create(o1).getId(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); + p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); + p1.setManagingOrganization(new ResourceReferenceDt(Organization.class, o1id)); + IdDt p1Id = ourClient.create(p1).getId(); + + //@formatter:off + Bundle actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.PROVIDER.hasId(o1id)) + .encodedJson().prettyPrint().execute(); + //@formatter:on + assertEquals(1, actual.size()); + assertEquals(p1Id, actual.getEntries().get(0).getId()); + + } + + @Test + public void testInsertBadReference() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); + p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); + p1.setManagingOrganization(new ResourceReferenceDt(Organization.class, "132312323")); + + try { + ourClient.create(p1).getId(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Organization/132312323")); + } + + } + + @AfterClass + public static void afterClass() throws Exception { + ourServer.stop(); + ourAppCtx.stop(); + } + + @SuppressWarnings("unchecked") + @BeforeClass + public static void beforeClass() throws Exception { + ourAppCtx = new ClassPathXmlApplicationContext("fhir-spring-test-config.xml"); + + patientDao = (IFhirResourceDao) ourAppCtx.getBean("myPatientDao", IFhirResourceDao.class); + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(patientDao); + + questionnaireDao = (IFhirResourceDao) ourAppCtx.getBean("myQuestionnaireDao", IFhirResourceDao.class); + QuestionnaireResourceProvider questionnaireRp = new QuestionnaireResourceProvider(); + questionnaireRp.setDao(questionnaireDao); + + observationDao = (IFhirResourceDao) ourAppCtx.getBean("myObservationDao", IFhirResourceDao.class); + ObservationResourceProvider observationRp = new ObservationResourceProvider(); + observationRp.setDao(observationDao); + + IFhirResourceDao organizationDao = (IFhirResourceDao) ourAppCtx.getBean("myOrganizationDao", IFhirResourceDao.class); + OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); + organizationRp.setDao(organizationDao); + + RestfulServer restServer = new RestfulServer(); + restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + int myPort = 8888; + ourServer = new Server(myPort); + + ServletContextHandler proxyHandler = new ServletContextHandler(); + proxyHandler.setContextPath("/"); + + RestfulServerTesterServlet testerServlet = new RestfulServerTesterServlet(); + String serverBase = "http://localhost:" + myPort + "/fhir/context"; + testerServlet.setServerBase(serverBase); + // testerServlet.setServerBase("http://fhir.healthintersections.com.au/open"); + ServletHolder handler = new ServletHolder(); + handler.setServlet(testerServlet); + proxyHandler.addServlet(handler, "/fhir/tester/*"); + + ServletHolder servletHolder = new ServletHolder(); + servletHolder.setServlet(restServer); + proxyHandler.addServlet(servletHolder, "/fhir/context/*"); + + ourServer.setHandler(proxyHandler); + ourServer.start(); + + ourCtx = restServer.getFhirContext(); + + ourClient = ourCtx.newRestfulGenericClient(serverBase); + } + +} diff --git a/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/JpaTestApp.java b/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/JpaTestApp.java index 4472a54b4c1..9dbae20df7b 100644 --- a/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/JpaTestApp.java +++ b/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/JpaTestApp.java @@ -10,6 +10,7 @@ import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Questionnaire; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.tester.RestfulServerTesterServlet; +import ca.uhn.test.jpasrv.PatientResourceProvider; public class JpaTestApp { diff --git a/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/PatientResourceProvider.java b/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/PatientResourceProvider.java deleted file mode 100644 index 4520b8b109a..00000000000 --- a/hapi-fhir-jpaserver-test/src/main/java/ca/uhn/fhir/jpa/test/PatientResourceProvider.java +++ /dev/null @@ -1,46 +0,0 @@ -package ca.uhn.fhir.jpa.test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ca.uhn.fhir.jpa.dao.BaseJpaResourceProvider; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.annotation.Description; -import ca.uhn.fhir.model.dstu.resource.Patient; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.Search; - -public class PatientResourceProvider extends BaseJpaResourceProvider { - - @Override - public Class getResourceType() { - return Patient.class; - } - - @Search - public List searchByName( - @Description(shortDefinition="Matches the patient's family (last) name") - @RequiredParam(name=Patient.SP_NAME) StringDt theFamily, - @Description(shortDefinition="Matches the patient's given (first) name") - @OptionalParam(name=Patient.SP_GIVEN) StringDt theGiven) { - - Map params = new HashMap<>(); - params.put(Patient.SP_NAME, theFamily); - params.put(Patient.SP_GIVEN, theGiven); - - return getDao().search(params); - } - - @Search - public List searchByGiven( - @Description(shortDefinition="Matches the patient's given (first) name") - @RequiredParam(name=Patient.SP_GIVEN) StringDt theValue - ) { - return getDao().search(Patient.SP_GIVEN, theValue); - } - -} diff --git a/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml b/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml index 0735a37a775..ba50d45d9b3 100644 --- a/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml +++ b/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml @@ -14,18 +14,29 @@ - + + + + + + + + + + - + - + + + diff --git a/hapi-fhir-jpaserver-base/src/main/resources/META-INF/persistence.xml b/hapi-fhir-jpaserver-test/src/main/resources/fhir_jpatest_persistence.xml similarity index 65% rename from hapi-fhir-jpaserver-base/src/main/resources/META-INF/persistence.xml rename to hapi-fhir-jpaserver-test/src/main/resources/fhir_jpatest_persistence.xml index 733fd3af60a..6d8a043aeb4 100644 --- a/hapi-fhir-jpaserver-base/src/main/resources/META-INF/persistence.xml +++ b/hapi-fhir-jpaserver-test/src/main/resources/fhir_jpatest_persistence.xml @@ -6,8 +6,21 @@ org.hibernate.ejb.HibernatePersistence - - false + ca.uhn.test.jpasrv.PatientResourceTable + ca.uhn.test.jpasrv.LocationResourceTable + ca.uhn.test.jpasrv.ObservationResourceTable + ca.uhn.test.jpasrv.OrganizationResourceTable + ca.uhn.test.jpasrv.QuestionnaireResourceTable + ca.uhn.fhir.jpa.entity.ResourceHistoryTable + ca.uhn.fhir.jpa.entity.ResourceHistoryTag + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString + ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken + ca.uhn.fhir.jpa.entity.ResourceLink + ca.uhn.fhir.jpa.entity.ResourceTag + + true 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 eb26b45504c..47cf085289e 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 @@ -45,7 +45,7 @@ public class TinderJpaRestServerMojo extends AbstractMojo { private String packageBase; @Parameter(required = true) - private List resources; + private List baseResourceNames; @Override public void execute() throws MojoExecutionException, MojoFailureException { @@ -54,14 +54,20 @@ public class TinderJpaRestServerMojo extends AbstractMojo { directoryBase.mkdirs(); ResourceGeneratorUsingSpreadsheet gen = new ResourceGeneratorUsingSpreadsheet(); - gen.setBaseResourceNames(resources); + gen.setBaseResourceNames(baseResourceNames); - gen.setFilenameSuffix("ResourceProvider"); - gen.setTemplate("/vm/jpa_resource_provider.vm"); try { gen.parse(); + + gen.setFilenameSuffix("ResourceProvider"); + gen.setTemplate("/vm/jpa_resource_provider.vm"); gen.writeAll(directoryBase, packageBase); + + gen.setFilenameSuffix("ResourceTable"); + gen.setTemplate("/vm/jpa_resource_table.vm"); + gen.writeAll(directoryBase, packageBase); + } catch (Exception e) { throw new MojoFailureException("Failed to generate server",e); } @@ -114,7 +120,7 @@ public class TinderJpaRestServerMojo extends AbstractMojo { TinderJpaRestServerMojo mojo = new TinderJpaRestServerMojo(); mojo.packageBase = "ca.uhn.test"; - mojo.resources =java.util.Collections.singletonList("patient"); + mojo.baseResourceNames =java.util.Collections.singletonList("patient"); mojo.targetDirectory = new File("target/gen"); mojo.execute(); } diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseRootType.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseRootType.java index f7cc33f071d..446b7d86ece 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseRootType.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/model/BaseRootType.java @@ -25,6 +25,16 @@ public abstract class BaseRootType extends BaseElement { return mySearchParameters; } + public List getSearchParametersWithoutComposite() { + ArrayList retVal = new ArrayList(); + for(SearchParameter next:getSearchParameters()) { + if(!next.getType().equals("composite")) { + retVal.add(next); + } + } + return retVal; + } + @Override public String getTypeSuffix() { return ""; diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java index 38e8747cdcb..d6855b502aa 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/parser/BaseStructureParser.java @@ -50,6 +50,7 @@ public abstract class BaseStructureParser { private TreeSet myImports = new TreeSet(); private Map myLocallyDefinedClassNames = new HashMap(); private List myResources = new ArrayList(); + private boolean myImportsResolved; public void addResource(BaseRootType theResource) { myResources.add(theResource); @@ -286,6 +287,7 @@ public abstract class BaseStructureParser { ctx.put("resourceBlockChildren", theResource.getResourceBlockChildren()); ctx.put("childExtensionTypes", ObjectUtils.defaultIfNull(myExtensions, new ArrayList())); ctx.put("searchParams", (theResource.getSearchParameters())); + ctx.put("searchParamsWithoutComposite", (theResource.getSearchParametersWithoutComposite())); VelocityEngine v = new VelocityEngine(); v.setProperty("resource.loader", "cp"); @@ -319,9 +321,12 @@ public abstract class BaseStructureParser { throw new MojoFailureException(theOutputDirectory + " is not a directory"); } - for (BaseRootType next : myResources) { - ourLog.info("Scanning resource for imports {}", next.getName()); - scanForImportsNames(next); + if (!myImportsResolved) { + for (BaseRootType next : myResources) { + ourLog.info("Scanning resource for imports {}", next.getName()); + scanForImportsNames(next); + } + myImportsResolved = true; } for (BaseRootType next : myResources) { @@ -330,7 +335,8 @@ public abstract class BaseStructureParser { scanForTypeNameConflicts(next); fixResourceReferenceClassNames(next, thePackageBase); -// File f = new File(theOutputDirectory, (next.getDeclaringClassNameComplete()) /*+ getFilenameSuffix()*/ + ".java"); + // File f = new File(theOutputDirectory, (next.getDeclaringClassNameComplete()) /*+ getFilenameSuffix()*/ + + // ".java"); File f = new File(theOutputDirectory, (next.getElementName()) + getFilenameSuffix() + ".java"); try { write(next, f, thePackageBase); @@ -348,9 +354,8 @@ public abstract class BaseStructureParser { // } /** - * Example: Encounter has an internal block class named "Location", but it - * also has a reference to the Location resource type, so we need to use the - * fully qualified name for that resource reference + * Example: Encounter has an internal block class named "Location", but it also has a reference to the Location + * resource type, so we need to use the fully qualified name for that resource reference */ private void fixResourceReferenceClassNames(BaseElement theNext, String thePackageBase) { for (BaseElement next : theNext.getChildren()) { 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 d2ddc081559..b70a02f44ad 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 @@ -23,8 +23,8 @@ public class ${className}ResourceProvider extends BaseJpaResourceProvider<${clas } @Search() - List<${className}> search( -#foreach ( $param in $searchParams ) #{if}(true) #{end} + public List<${className}> search( +#foreach ( $param in $searchParamsWithoutComposite ) #{if}(true) #{end} @Description(shortDefinition="${param.description}") @OptionalParam(name="${param.name}") @@ -40,12 +40,14 @@ public class ${className}ResourceProvider extends BaseJpaResourceProvider<${clas QuantityDt the${param.nameCapitalized} #{if}($foreach.hasNext), #{end} #elseif (${param.type} == 'reference' ) ReferenceParam the${param.nameCapitalized} #{if}($foreach.hasNext), #{end} +#elseif (${param.type} == 'composite' ) + ReferenceParam the${param.nameCapitalized} #{if}($foreach.hasNext), #{end} #end #end ) { SearchParameterMap paramMap = new SearchParameterMap(); -#foreach ( $param in $searchParams ) +#foreach ( $param in $searchParamsWithoutComposite ) paramMap.add("${param.name}", the${param.nameCapitalized}); #end diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_table.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_table.vm index 2cb12705924..04cfae2d869 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_table.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_table.vm @@ -3,13 +3,13 @@ package ${packageBase}; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; -import ca.uhn.fhir.model.dstu.resource.*; -public class ${className}ResourceTable extends BaseResourceTable<${className}> { +import ca.uhn.fhir.jpa.entity.BaseResourceTable; +import ca.uhn.fhir.model.dstu.resource.*; @Entity @DiscriminatorValue("${className}") -public class PatientResourceTable extends BaseResourceTable { +public class ${className}ResourceTable extends BaseResourceTable<${className}> { @Override public Class<${className}> getResourceType() {