Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
af64f1b9e1
|
@ -225,6 +225,13 @@
|
|||
<version>9.1.1.v20140108</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
<version>9.1.1.v20140108</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- UNIT TEST DEPENDENCIES -->
|
||||
<dependency>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
Bernard Gitaadji for reporting and diagnosing the issue!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.6" date="TBD">
|
||||
<release version="0.6" date="2014-Sep-08" description="This release brings a number of new features and bug fixes!">
|
||||
<!--
|
||||
<action type="add">
|
||||
Allow generic client ... OAUTH
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
<?xml version="1.0"?>
|
||||
<document xmlns="http://maven.apache.org/changes/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 ./changes.xsd">
|
||||
<properties>
|
||||
<author>James Agnew</author>
|
||||
<title>HAPI FHIR Changelog</title>
|
||||
</properties>
|
||||
<body>
|
||||
<<<<<<< HEAD
|
||||
<release version="0.6" date="2014-Sep-08" description="This release brings a number of new features and bug fixes!">
|
||||
=======
|
||||
<release version="0.7" date="TBD">
|
||||
<action type="add">
|
||||
Documentation update, thanks to Suranga Nath Kasthurirathne of the OpenMRS project.
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.6" date="TBD">
|
||||
>>>>>>> cdd4b137fb40dd72f895c2bcb644d6e668e1015b
|
||||
<!--
|
||||
<action type="add">
|
||||
Allow generic client ... OAUTH
|
||||
</action>
|
||||
-->
|
||||
<action type="add">
|
||||
Add server interceptor framework, and new interceptor for logging incoming
|
||||
requests.
|
||||
</action>
|
||||
<action type="add">
|
||||
Add server validation framework for validating resources against the FHIR schemas and schematrons
|
||||
</action>
|
||||
<action type="fix">
|
||||
Tester UI created double _format and _pretty param entries in searches. Thanks to Gered King of University
|
||||
Health Network for reporting!
|
||||
</action>
|
||||
<action type="fix" issue="4">
|
||||
Create method was incorrectly returning an HTTP 204 on sucessful completion, but
|
||||
should be returning an HTTP 200 per the FHIR specification. Thanks to wanghaisheng
|
||||
for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
FHIR Tester UI now correctly sends UTF-8 charset in responses so that message payloads containing
|
||||
non US-ASCII characters will correctly display in the browser
|
||||
</action>
|
||||
<action type="fix">
|
||||
JSON parser was incorrectly encoding extensions on composite elements outside the element itself
|
||||
(as is done correctly for non-composite elements) instead of inside of them. Thanks to David Hay of
|
||||
Orion for reporting this!
|
||||
</action>
|
||||
<action type="add">
|
||||
Contained/included resource instances received by a client are now automatically
|
||||
added to any ResourceReferenceDt instancea in other resources which reference them.
|
||||
</action>
|
||||
<action type="add">
|
||||
Add documentation on how to use eBay CORS Filter to support Cross Origin Resource
|
||||
Sharing (CORS) to server. CORS support that was built in to the server itself has
|
||||
been removed, as it did not work correctly (and was reinventing a wheel that others
|
||||
have done a great job inventing). Thanks to Peter Bernhardt of Relay Health for all the assistance
|
||||
in testing this!
|
||||
</action>
|
||||
<action type="fix">
|
||||
IResource interface did not expose the getLanguage/setLanguage methods from BaseResource,
|
||||
so the resource language was difficult to access.
|
||||
</action>
|
||||
<action type="fix">
|
||||
JSON Parser now gives a more friendly error message if it tries to parse JSON with invalid use
|
||||
of single quotes
|
||||
</action>
|
||||
<action type="add">
|
||||
Transaction server method is now allowed to return an OperationOutcome in addition to the
|
||||
incoming resources. The public test server now does this in order to return status information
|
||||
about the transaction processing.
|
||||
</action>
|
||||
<action type="add">
|
||||
Update method in the server can now flag (via a field on the MethodOutcome object being returned)
|
||||
that the result was actually a creation, and Create method can indicate that it was actually an
|
||||
update. This has no effect other than to switch between the HTTP 200 and HTTP 201 status codes on the
|
||||
response, but this may be useful in some circumstances.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Annotation client search methods with a specific resource type (e.g. List<Patient> search())
|
||||
won't return any resources that aren't of the correct type that are received in a response
|
||||
bundle (generally these are referenced resources, so they are populated in the reference fields instead).
|
||||
Thanks to Tahura Chaudhry of University Health Network for the unit test!
|
||||
</action>
|
||||
<action type="add">
|
||||
Added narrative generator template for OperationOutcome resource
|
||||
</action>
|
||||
<action type="fix">
|
||||
Date/time types did not correctly parse values in the format "yyyymmdd" (although the FHIR-defined format
|
||||
is "yyyy-mm-dd" anyhow, and this is correctly handled). Thanks to Jeffrey Ting of Systems Made Simple
|
||||
for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server search method for an unnamed query gets called if the client requests a named query
|
||||
with the same parameter list. Thanks to Neal Acharya of University Health Network for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Category header (for tags) is correctly read in client for "read" operation
|
||||
</action>
|
||||
<action type="add">
|
||||
Transaction method in server can now have parameter type Bundle instead of
|
||||
List<IResource>
|
||||
</action>
|
||||
<action type="add">
|
||||
HAPI parsers now use field access to get/set values instead of method accessors and mutators.
|
||||
This should give a small performance boost.
|
||||
</action>
|
||||
<action type="fix">
|
||||
JSON parser encodes resource references incorrectly, using the name "resource" instead
|
||||
of the name "reference" for the actual reference. Thanks to
|
||||
Ricky Nguyen for reporting and tracking down the issue!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Rename NotImpementedException to NotImplementedException (to correct typo)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with 4xx/5xx status)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix performance issue in date/time datatypes where pattern matchers were not static
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, but
|
||||
previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Resource of type "List" failed to parse from a bundle correctly. Thanks to David Hay of Orion Health
|
||||
for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
QuantityParam correctly encodes approximate (~) prefix to values
|
||||
</action>
|
||||
<action type="fix" issue="14">
|
||||
If a server defines a method with parameter "_id", incoming search requests for that method may
|
||||
get delegated to the wrong method. Thanks to Neal Acharya for reporting!
|
||||
</action>
|
||||
<action type="add">
|
||||
SecurityEvent.Object structural element has been renamed to
|
||||
SecurityEvent.ObjectElement to avoid conflicting names with the
|
||||
java Object class. Thanks to Laurie Macdougall-Sookraj of UHN for
|
||||
reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Text/narrative blocks that were created with a non-empty
|
||||
namespace prefix (e.g. <xhtml:div xmlns:xhtml="...">...</xhtml:div>)
|
||||
failed to encode correctly (prefix was missing in encoded resource)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Resource references previously encoded their children (display and reference)
|
||||
in the wrong order so references with both would fail schema validation.
|
||||
</action>
|
||||
<action type="add">
|
||||
SecurityEvent resource's enums now use friendly enum names instead of the unfriendly
|
||||
numeric code values. Thanks to Laurie MacDougall-Sookraj of UHN for the
|
||||
suggestion!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.5" date="2014-Jul-30">
|
||||
<action type="add">
|
||||
having multiple ways of accomplishing the same thing. This means that a number of existing classes
|
||||
have been deprocated in favour of new naming schemes.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
All annotation-based clients and all server search method parameters are now named
|
||||
(type)Param, for example: StringParam, TokenParam, etc.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
All generic/fluent client method parameters are now named
|
||||
(type)ClientParam, for example: StringClientParam, TokenClientParam, etc.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
All renamed classes have been retained and deprocated, so this change should not cause any issues
|
||||
for existing applications but those applications should be refactored to use the
|
||||
new parameters when possible.
|
||||
</action>
|
||||
<action type="add">
|
||||
Allow server methods to return wildcard generic types (e.g. List<? extends IResource>)
|
||||
</action>
|
||||
<action type="add">
|
||||
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
|
||||
"&identifier=system|codepart1\|codepart2"
|
||||
</action>
|
||||
<action type="add">
|
||||
Add support for OPTIONS verb (which returns the server conformance statement)
|
||||
</action>
|
||||
<action type="add">
|
||||
Add support for CORS headers in server
|
||||
</action>
|
||||
<action type="add">
|
||||
Bump SLF4j dependency to latest version (1.7.7)
|
||||
</action>
|
||||
<action type="add">
|
||||
Add interceptor framework for clients (annotation based and generic), and add interceptors
|
||||
for configurable logging, capturing requests and responses, and HTTP basic auth.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead
|
||||
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
|
||||
</action>
|
||||
<action type="add">
|
||||
Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion!
|
||||
</action>
|
||||
<action type="add" issue="1">
|
||||
If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or
|
||||
an OperationOutcome resource, include the message in the exception message so that it will be
|
||||
more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion!
|
||||
</action>
|
||||
<action type="add" issue="2">
|
||||
Read invocations in the client now process the "Content-Location" header and use it to
|
||||
populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion!
|
||||
</action>
|
||||
<action type="fix" issue="3">
|
||||
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
|
||||
defined. Thanks to Neal Acharya from UHN for surfacing this one!
|
||||
</action>
|
||||
<action type="add">
|
||||
Binary reads on a server not include the Content-Disposition header, to prevent HTML in binary
|
||||
blobs from being used for nefarious purposes. See
|
||||
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=3298">FHIR Tracker Bug 3298</a>]]>
|
||||
for more information.
|
||||
</action>
|
||||
<action type="add">
|
||||
Support has been added for using an HTTP proxy for outgoing requests.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix: Primitive extensions declared against custom resource types
|
||||
are encoded even if they have no value. Thanks to David Hay of Orion for
|
||||
reporting this!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix: RESTful server deployed to a location where the URL to access it contained a
|
||||
space (e.g. a WAR file with a space in the name) failed to work correctly.
|
||||
Thanks to David Hay of Orion for reporting this!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.4" date="2014-Jul-13">
|
||||
<action type="add">
|
||||
<![CDATA[<b>BREAKING CHANGE:</b>]]>: IdDt has been modified so that it
|
||||
contains a partial or complete resource identity. Previously it contained
|
||||
only the simple alphanumeric id of the resource (the part at the end of the "read" URL for
|
||||
that resource) but it can now contain a complete URL or even a partial URL (e.g. "Patient/123")
|
||||
and can optionally contain a version (e.g. "Patient/123/_history/456"). New methods have
|
||||
been added to this datatype which provide just the numeric portion. See the JavaDoc
|
||||
for more information.
|
||||
</action>
|
||||
<action type="add">
|
||||
<![CDATA[<b>API CHANGE:</b>]]>: Most elements in the HAPI FHIR model contain
|
||||
a getId() and setId() method. This method is confusing because it is only actually used
|
||||
for IDREF elements (which are rare) but its name makes it easy to confuse with more
|
||||
important identifiers. For this reason, these methods have been deprocated and replaced with
|
||||
get/setElementSpecificId() methods. The old methods will be removed at some point. Resource
|
||||
types are unchanged and retain their get/setId methods.
|
||||
</action>
|
||||
<action type="add">
|
||||
Allow use of QuantityDt as a service parameter to support the "quantity" type. Previously
|
||||
QuantityDt did not implement IQueryParameterType so it was not valid, and there was no way to
|
||||
support quantity search parameters on the server (e.g. Observation.value-quantity)
|
||||
</action>
|
||||
<action type="add">
|
||||
Introduce StringParameter type which can be used as a RESTful operation search parameter
|
||||
type. StringParameter allows ":exact" matches to be specified in clients, and handled in servers.
|
||||
</action>
|
||||
<action type="add">
|
||||
Parsers (XML/JSON) now support deleted entries in bundles
|
||||
</action>
|
||||
<action type="add">
|
||||
Transaction method now supported in servers
|
||||
</action>
|
||||
<action type="add">
|
||||
Support for Binary resources added (in servers, clients, parsers, etc.)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Support for Query resources fixed (in parser)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a resource)
|
||||
now parse and encode correctly, meaning that all contained resources are placed in the "contained" element
|
||||
of the root resource, and the parser looks in the root resource for all container levels when stitching
|
||||
contained resources back together.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server methods with @Include parameter would sometimes fail when no _include was actually
|
||||
specified in query strings.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Client requests for IdentifierDt types (such as Patient.identifier) did not create the correct
|
||||
query string if the system is null.
|
||||
</action>
|
||||
<action type="add">
|
||||
Add support for paging responses from RESTful servers.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are
|
||||
produced by the Health Intersections server)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server now automatically compresses responses if the client indicates support
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server failed to support optional parameters when type is String and :exact qualifier is used
|
||||
</action>
|
||||
<action type="fix">
|
||||
Read method in client correctly populated resource ID in returned object
|
||||
</action>
|
||||
<action type="add">
|
||||
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.3" date="2014-May-12" description="This release corrects lots of bugs and introduces the fluent client mode">
|
||||
</release>
|
||||
</body>
|
||||
</document>
|
|
@ -107,6 +107,8 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
|
|||
|
||||
if (IResource.class.isAssignableFrom(next)) {
|
||||
myDatatypeToElementDefinition.put(ResourceReferenceDt.class, nextDef);
|
||||
alternateElementName = getElementName() + "Resource";
|
||||
myDatatypeToElementName.put(ResourceReferenceDt.class, alternateElementName);
|
||||
}
|
||||
|
||||
myDatatypeToElementDefinition.put(next, nextDef);
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.lang.annotation.Target;
|
|||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
|
||||
|
||||
|
@ -76,5 +77,12 @@ public @interface Search {
|
|||
*/
|
||||
// NB: Read, Search (maybe others) share this annotation method, so update the javadocs everywhere
|
||||
Class<? extends IResource> type() default IResource.class;
|
||||
|
||||
/**
|
||||
* This is an experimental option - Use with caution
|
||||
*
|
||||
* @see IDynamicSearchResourceProvider
|
||||
*/
|
||||
boolean dynamic() default false;
|
||||
|
||||
}
|
||||
|
|
|
@ -63,8 +63,10 @@ import ca.uhn.fhir.rest.server.BundleProviders;
|
|||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
@ -82,7 +84,6 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
|||
private FhirContext myContext;
|
||||
private Method myMethod;
|
||||
private List<IParameter> myParameters;
|
||||
|
||||
private Object myProvider;
|
||||
|
||||
public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
|
@ -92,7 +93,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
|||
myMethod = theMethod;
|
||||
myContext = theContext;
|
||||
myProvider = theProvider;
|
||||
myParameters = MethodUtil.getResourceParameters(theMethod);
|
||||
myParameters = MethodUtil.getResourceParameters(theMethod, theProvider);
|
||||
}
|
||||
|
||||
public List<Class<?>> getAllowableParamAnnotations() {
|
||||
|
@ -349,7 +350,12 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
|||
if (read != null) {
|
||||
return new ReadMethodBinding(returnType, theMethod, theContext, theProvider);
|
||||
} else if (search != null) {
|
||||
return new SearchMethodBinding(returnType, theMethod, theContext, theProvider);
|
||||
if (search.dynamic()) {
|
||||
IDynamicSearchResourceProvider provider = (IDynamicSearchResourceProvider) theProvider;
|
||||
return new DynamicSearchMethodBinding(returnType, theMethod, theContext, provider);
|
||||
} else {
|
||||
return new SearchMethodBinding(returnType, theMethod, theContext, theProvider);
|
||||
}
|
||||
} else if (conformance != null) {
|
||||
return new ConformanceMethodBinding(theMethod, theContext, theProvider);
|
||||
} else if (create != null) {
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package ca.uhn.fhir.rest.method;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
private IDynamicSearchResourceProvider myProvider;
|
||||
private List<RuntimeSearchParam> mySearchParameters;
|
||||
private HashSet<String> myParamNames;
|
||||
private Integer myIdParamIndex;
|
||||
|
||||
public DynamicSearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, IDynamicSearchResourceProvider theProvider) {
|
||||
super(theReturnResourceType, theMethod, theConetxt, theProvider);
|
||||
|
||||
myProvider = theProvider;
|
||||
mySearchParameters = myProvider.getSearchParameters();
|
||||
|
||||
myParamNames = new HashSet<String>();
|
||||
for (RuntimeSearchParam next : mySearchParameters) {
|
||||
myParamNames.add(next.getName());
|
||||
}
|
||||
|
||||
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IParameter> getParameters() {
|
||||
List<IParameter> retVal = new ArrayList<IParameter>(super.getParameters());
|
||||
|
||||
for (RuntimeSearchParam next : mySearchParameters) {
|
||||
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnTypeEnum getReturnType() {
|
||||
return ReturnTypeEnum.BUNDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||
if (myIdParamIndex != null) {
|
||||
theMethodParams[myIdParamIndex] = theRequest.getId();
|
||||
}
|
||||
|
||||
Object response = invokeServerMethod(theMethodParams);
|
||||
return toResourceList(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||
return RestfulOperationTypeEnum.SEARCH_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestfulOperationSystemEnum getSystemOperationType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DynamicSearchMethodBinding.class);
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
|
||||
if (!theRequest.getResourceName().equals(getResourceName())) {
|
||||
ourLog.trace("Method {} doesn't match because resource name {} != {}", getMethod().getName(), theRequest.getResourceName(), getResourceName());
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getId() != null && myIdParamIndex == null) {
|
||||
ourLog.trace("Method {} doesn't match because ID is not null: {}", theRequest.getId());
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getRequestType() == RequestType.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
|
||||
ourLog.trace("Method {} doesn't match because request type is GET but operation is not null: {}", theRequest.getId(), theRequest.getOperation());
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getRequestType() == RequestType.POST && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {
|
||||
ourLog.trace("Method {} doesn't match because request type is POST but operation is not _search: {}", theRequest.getId(), theRequest.getOperation());
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getRequestType() != RequestType.GET && theRequest.getRequestType() != RequestType.POST) {
|
||||
ourLog.trace("Method {} doesn't match because request type is {}", getMethod());
|
||||
return false;
|
||||
}
|
||||
if (theRequest.getCompartmentName() != null) {
|
||||
ourLog.trace("Method {} doesn't match because it is for compartment {}", new Object[] { getMethod(), theRequest.getCompartmentName() });
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String next : theRequest.getParameters().keySet()) {
|
||||
if (next.charAt(0) == '_') {
|
||||
continue;
|
||||
}
|
||||
String nextQualified = next;
|
||||
int colonIndex = next.indexOf(':');
|
||||
int dotIndex = next.indexOf('.');
|
||||
if (colonIndex != -1 || dotIndex != -1) {
|
||||
int index;
|
||||
if (colonIndex != -1 && dotIndex != -1) {
|
||||
index = Math.min(colonIndex, dotIndex);
|
||||
} else {
|
||||
index = (colonIndex != -1) ? colonIndex : dotIndex;
|
||||
}
|
||||
next = next.substring(0, index);
|
||||
}
|
||||
if (!myParamNames.contains(next)) {
|
||||
ourLog.trace("Method {} doesn't match because has parameter {}", new Object[] { getMethod(), nextQualified });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
||||
// there should be no way to call this....
|
||||
throw new UnsupportedOperationException("Dynamic search methods are only used for server implementations");
|
||||
}
|
||||
|
||||
public Collection<? extends RuntimeSearchParam> getSearchParams() {
|
||||
return mySearchParameters;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package ca.uhn.fhir.rest.method;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||
import ca.uhn.fhir.rest.param.CompositeOrListParam;
|
||||
import ca.uhn.fhir.rest.param.DateOrListParam;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.NumberOrListParam;
|
||||
import ca.uhn.fhir.rest.param.NumberParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityOrListParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class DynamicSearchParameter implements IParameter {
|
||||
|
||||
private Map<String, RuntimeSearchParam> myNameToParam = new HashMap<String, RuntimeSearchParam>();
|
||||
|
||||
public DynamicSearchParameter(IDynamicSearchResourceProvider theProvider) {
|
||||
for (RuntimeSearchParam next : theProvider.getSearchParameters()) {
|
||||
myNameToParam.put(next.getName(), next);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, BaseHttpClientInvocation theClientInvocation) throws InternalErrorException {
|
||||
throw new UnsupportedOperationException("Dynamic search is not supported in client mode (use fluent client for dynamic-like searches)");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
|
||||
SearchParameterMap retVal = new SearchParameterMap();
|
||||
|
||||
for (String next : theRequest.getParameters().keySet()) {
|
||||
String qualifier = null;
|
||||
String qualifiedParamName = next;
|
||||
RuntimeSearchParam param = myNameToParam.get(next);
|
||||
if (param == null) {
|
||||
int colonIndex = next.indexOf(':');
|
||||
int dotIndex = next.indexOf('.');
|
||||
if (colonIndex != -1 || dotIndex != -1) {
|
||||
int index;
|
||||
if (colonIndex != -1 && dotIndex != -1) {
|
||||
index = Math.min(colonIndex, dotIndex);
|
||||
} else {
|
||||
index = (colonIndex != -1) ? colonIndex : dotIndex;
|
||||
}
|
||||
qualifier = next.substring(index);
|
||||
next = next.substring(0, index);
|
||||
param = myNameToParam.get(next);
|
||||
}
|
||||
}
|
||||
|
||||
if (param != null) {
|
||||
|
||||
for (String nextValue : theRequest.getParameters().get(qualifiedParamName)) {
|
||||
QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, nextValue);
|
||||
|
||||
switch (param.getParamType()) {
|
||||
case COMPOSITE:
|
||||
Class<? extends IQueryParameterType> left = toParamType(param.getCompositeOf().get(0));
|
||||
Class<? extends IQueryParameterType> right = toParamType(param.getCompositeOf().get(0));
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
CompositeOrListParam compositeOrListParam = new CompositeOrListParam(left, right);
|
||||
compositeOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, compositeOrListParam);
|
||||
break;
|
||||
case DATE:
|
||||
DateOrListParam dateOrListParam = new DateOrListParam();
|
||||
dateOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, dateOrListParam);
|
||||
break;
|
||||
case NUMBER:
|
||||
NumberOrListParam numberOrListParam = new NumberOrListParam();
|
||||
numberOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, numberOrListParam);
|
||||
break;
|
||||
case QUANTITY:
|
||||
QuantityOrListParam quantityOrListParam = new QuantityOrListParam();
|
||||
quantityOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, quantityOrListParam);
|
||||
break;
|
||||
case REFERENCE:
|
||||
ReferenceOrListParam referenceOrListParam = new ReferenceOrListParam();
|
||||
referenceOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, referenceOrListParam);
|
||||
break;
|
||||
case STRING:
|
||||
StringOrListParam stringOrListParam = new StringOrListParam();
|
||||
stringOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, stringOrListParam);
|
||||
break;
|
||||
case TOKEN:
|
||||
TokenOrListParam tokenOrListParam = new TokenOrListParam();
|
||||
tokenOrListParam.setValuesAsQueryTokens(paramList);
|
||||
retVal.add(next, tokenOrListParam);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Class<? extends IQueryParameterType> toParamType(RuntimeSearchParam theRuntimeSearchParam) {
|
||||
switch (theRuntimeSearchParam.getParamType()) {
|
||||
case COMPOSITE:
|
||||
throw new IllegalStateException("Composite subtype");
|
||||
case DATE:
|
||||
return DateParam.class;
|
||||
case NUMBER:
|
||||
return NumberParam.class;
|
||||
case QUANTITY:
|
||||
return QuantityParam.class;
|
||||
case REFERENCE:
|
||||
return ReferenceParam.class;
|
||||
case STRING:
|
||||
return StringParam.class;
|
||||
case TOKEN:
|
||||
return TokenParam.class;
|
||||
default:
|
||||
throw new IllegalStateException("null type");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package ca.uhn.fhir.rest.method;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PushbackReader;
|
||||
|
@ -45,6 +44,7 @@ import ca.uhn.fhir.rest.annotation.IncludeParam;
|
|||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.ServerBase;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.Sort;
|
||||
|
@ -57,6 +57,8 @@ import ca.uhn.fhir.rest.param.CollectionBinder;
|
|||
import ca.uhn.fhir.rest.param.ResourceParameter;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.SearchParameterMap;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
|
||||
/*
|
||||
|
@ -91,7 +93,7 @@ public class MethodUtil {
|
|||
urlBuilder.append(resourceName);
|
||||
urlBuilder.append('/');
|
||||
urlBuilder.append(theId.getIdPart());
|
||||
|
||||
|
||||
HttpPutClientInvocation retVal;
|
||||
String urlExtension = urlBuilder.toString();
|
||||
if (StringUtils.isBlank(theResourceBody)) {
|
||||
|
@ -109,10 +111,10 @@ public class MethodUtil {
|
|||
urlBuilder.append(versionId);
|
||||
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, urlBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
addTagsToPostOrPut(theResource, retVal);
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -135,7 +137,7 @@ public class MethodUtil {
|
|||
String headerValue = clHeaders.get(0);
|
||||
resource.getId().setValue(headerValue);
|
||||
}
|
||||
|
||||
|
||||
List<String> categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC);
|
||||
if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) {
|
||||
TagList tagList = new TagList();
|
||||
|
@ -223,7 +225,6 @@ public class MethodUtil {
|
|||
|
||||
}
|
||||
|
||||
|
||||
static void addTagsToPostOrPut(IResource resource, BaseHttpClientInvocation retVal) {
|
||||
TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
|
||||
if (list != null) {
|
||||
|
@ -265,8 +266,7 @@ public class MethodUtil {
|
|||
return new HttpGetClientInvocation("metadata");
|
||||
}
|
||||
|
||||
public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader,
|
||||
Map<String, List<String>> theHeaders) {
|
||||
public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) {
|
||||
List<String> locationHeaders = new ArrayList<String>();
|
||||
List<String> lh = theHeaders.get(Constants.HEADER_LOCATION_LC);
|
||||
if (lh != null) {
|
||||
|
@ -378,7 +378,7 @@ public class MethodUtil {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<IParameter> getResourceParameters(Method theMethod) {
|
||||
public static List<IParameter> getResourceParameters(Method theMethod, Object theProvider) {
|
||||
List<IParameter> parameters = new ArrayList<IParameter>();
|
||||
|
||||
Class<?>[] parameterTypes = theMethod.getParameterTypes();
|
||||
|
@ -389,7 +389,14 @@ public class MethodUtil {
|
|||
Class<?> parameterType = parameterTypes[paramIndex];
|
||||
Class<? extends java.util.Collection<?>> outerCollectionType = null;
|
||||
Class<? extends java.util.Collection<?>> innerCollectionType = null;
|
||||
if (TagList.class.isAssignableFrom(parameterType)) {
|
||||
if (SearchParameterMap.class.equals(parameterType)) {
|
||||
if (theProvider instanceof IDynamicSearchResourceProvider) {
|
||||
Search searchAnnotation = theMethod.getAnnotation(Search.class);
|
||||
if (searchAnnotation != null && searchAnnotation.dynamic()) {
|
||||
param = new DynamicSearchParameter((IDynamicSearchResourceProvider) theProvider);
|
||||
}
|
||||
}
|
||||
} else if (TagList.class.isAssignableFrom(parameterType)) {
|
||||
// TagList is handled directly within the method bindings
|
||||
param = new NullParameter();
|
||||
} else {
|
||||
|
@ -403,8 +410,7 @@ public class MethodUtil {
|
|||
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
}
|
||||
}
|
||||
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
|
||||
|
@ -443,19 +449,16 @@ public class MethodUtil {
|
|||
instantiableCollectionType = null;
|
||||
specType = String.class;
|
||||
} else if ((parameterType != Include.class && parameterType != PathSpecification.class) || innerCollectionType == null || outerCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
|
||||
+ Include.class.getSimpleName() + ">");
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + Include.class.getSimpleName() + ">");
|
||||
} else {
|
||||
instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName()
|
||||
+ "'");
|
||||
instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
|
||||
specType = parameterType;
|
||||
}
|
||||
|
||||
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType);
|
||||
} else if (nextAnnotation instanceof ResourceParam) {
|
||||
if (!IResource.class.isAssignableFrom(parameterType)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName()
|
||||
+ " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
|
||||
}
|
||||
param = new ResourceParameter((Class<? extends IResource>) parameterType);
|
||||
} else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
|
||||
|
@ -479,8 +482,8 @@ public class MethodUtil {
|
|||
}
|
||||
|
||||
if (param == null) {
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
|
||||
|
|
|
@ -23,7 +23,6 @@ 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;
|
||||
|
||||
|
@ -41,7 +40,7 @@ public class QualifiedParamList extends ArrayList<String> {
|
|||
super(theCapacity);
|
||||
}
|
||||
|
||||
public QualifiedParamList(FhirContext theContext, IQueryParameterOr<?> theNextOr) {
|
||||
public QualifiedParamList(IQueryParameterOr<?> theNextOr) {
|
||||
for (IQueryParameterType next : theNextOr.getValuesAsQueryTokens()) {
|
||||
if (myQualifier==null) {
|
||||
myQualifier=next.getQueryParameterQualifier();
|
||||
|
|
|
@ -60,7 +60,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
private Class<? extends IResource> myDeclaredResourceType;
|
||||
private String myDescription;
|
||||
private Integer myIdParamIndex;
|
||||
|
||||
private String myQueryName;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -118,40 +117,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
}
|
||||
|
||||
public static QualifierDetails extractQualifiersFromParameterName(String theParamName) {
|
||||
QualifierDetails retVal = new QualifierDetails();
|
||||
if (theParamName == null || theParamName.length() == 0) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int dotIdx = -1;
|
||||
int colonIdx = -1;
|
||||
for (int idx = 0; idx < theParamName.length(); idx++) {
|
||||
char nextChar = theParamName.charAt(idx);
|
||||
if (nextChar == '.' && dotIdx == -1) {
|
||||
dotIdx = idx;
|
||||
} else if (nextChar == ':' && colonIdx == -1) {
|
||||
colonIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (dotIdx != -1 && colonIdx != -1) {
|
||||
if (dotIdx < colonIdx) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx));
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
} else {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx));
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
}
|
||||
} else if (dotIdx != -1) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
} else if (colonIdx != -1) {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public Class<? extends IResource> getDeclaredResourceType() {
|
||||
return myDeclaredResourceType;
|
||||
}
|
||||
|
@ -322,6 +287,15 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
}
|
||||
|
||||
public void setResourceType(Class<? extends IResource> resourceType) {
|
||||
this.myDeclaredResourceType = resourceType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getMethod().toString();
|
||||
}
|
||||
|
||||
private List<String> processWhitelistAndBlacklist(List<String> theQualifiedNames, Set<String> theQualifierWhitelist, Set<String> theQualifierBlacklist) {
|
||||
if (theQualifierWhitelist == null && theQualifierBlacklist == null) {
|
||||
return theQualifiedNames;
|
||||
|
@ -337,10 +311,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public void setResourceType(Class<? extends IResource> resourceType) {
|
||||
this.myDeclaredResourceType = resourceType;
|
||||
}
|
||||
|
||||
public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theResourceName, Map<String, List<String>> theParameters, IdDt theId, String theCompartmentName,
|
||||
SearchStyleEnum theSearchStyle) {
|
||||
SearchStyleEnum searchStyle = theSearchStyle;
|
||||
|
@ -402,6 +372,40 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return invocation;
|
||||
}
|
||||
|
||||
public static QualifierDetails extractQualifiersFromParameterName(String theParamName) {
|
||||
QualifierDetails retVal = new QualifierDetails();
|
||||
if (theParamName == null || theParamName.length() == 0) {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int dotIdx = -1;
|
||||
int colonIdx = -1;
|
||||
for (int idx = 0; idx < theParamName.length(); idx++) {
|
||||
char nextChar = theParamName.charAt(idx);
|
||||
if (nextChar == '.' && dotIdx == -1) {
|
||||
dotIdx = idx;
|
||||
} else if (nextChar == ':' && colonIdx == -1) {
|
||||
colonIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
if (dotIdx != -1 && colonIdx != -1) {
|
||||
if (dotIdx < colonIdx) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx, colonIdx));
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
} else {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx, dotIdx));
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
}
|
||||
} else if (dotIdx != -1) {
|
||||
retVal.setDotQualifier(theParamName.substring(dotIdx));
|
||||
} else if (colonIdx != -1) {
|
||||
retVal.setColonQualifier(theParamName.substring(colonIdx));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static class QualifierDetails {
|
||||
|
||||
private String myColonQualifier;
|
||||
|
@ -467,11 +471,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getMethod().toString();
|
||||
}
|
||||
|
||||
public static enum RequestType {
|
||||
DELETE, GET, OPTIONS, POST, PUT
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
|
||||
List<IQueryParameterOr<?>> val = myParamBinder.encode(theContext, theObject);
|
||||
for (IQueryParameterOr<?> nextOr : val) {
|
||||
retVal.add(new QualifiedParamList(theContext, nextOr));
|
||||
retVal.add(new QualifiedParamList(nextOr));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
|
|
@ -28,10 +28,14 @@ import ca.uhn.fhir.model.api.IQueryParameterOr;
|
|||
import ca.uhn.fhir.rest.method.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implements IQueryParameterAnd<T> {
|
||||
public abstract class BaseAndListParam<T extends IQueryParameterOr<?>> implements IQueryParameterAnd<T> {
|
||||
|
||||
private List<T> myValues=new ArrayList<T>();
|
||||
|
||||
public void addValue(T theValue) {
|
||||
myValues.add(theValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(List<QualifiedParamList> theParameters) throws InvalidRequestException {
|
||||
myValues.clear();
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.List;
|
|||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
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;
|
||||
|
@ -33,10 +34,10 @@ abstract class BaseOrListParam<T extends IQueryParameterType> implements IQueryP
|
|||
|
||||
private List<T> myList=new ArrayList<T>();
|
||||
|
||||
public void addToken(T theParam) {
|
||||
Validate.notNull(theParam,"Param can not be null");
|
||||
myList.add(theParam);
|
||||
}
|
||||
// public void addToken(T theParam) {
|
||||
// Validate.notNull(theParam,"Param can not be null");
|
||||
// myList.add(theParam);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
|
|
|
@ -117,7 +117,7 @@ public class CodingListParam implements IQueryParameterOr<IQueryParameterType>,
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
getCodings().clear();
|
||||
for (String string : theParameters) {
|
||||
CodingDt dt = new CodingDt();
|
||||
|
|
|
@ -34,6 +34,14 @@ public class CompositeOrListParam<A extends IQueryParameterType, B extends IQuer
|
|||
myRightType = theRightType;
|
||||
}
|
||||
|
||||
public Class<A> getLeftType() {
|
||||
return myLeftType;
|
||||
}
|
||||
|
||||
public Class<B> getRightType() {
|
||||
return myRightType;
|
||||
}
|
||||
|
||||
@Override
|
||||
CompositeParam<A,B> newInstance() {
|
||||
return new CompositeParam<A,B>(myLeftType, myRightType);
|
||||
|
|
|
@ -154,7 +154,7 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
myBase.setMissing(null);
|
||||
myComparator = null;
|
||||
setValueAsString(null);
|
||||
|
@ -164,6 +164,7 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
} else if (theParameters.size() > 1) {
|
||||
throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -59,7 +59,7 @@ public class IdentifierListParam implements IQueryParameterOr<IQueryParameterTyp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
for (String string : theParameters) {
|
||||
IdentifierDt dt = new IdentifierDt();
|
||||
dt.setValueAsQueryToken(null, string);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
|
||||
/**
|
||||
* This is still an experimental API - It isn't meant for public consumption yet. Get in touch if you'd like to use it
|
||||
* and maybe we can help work out a good design together.
|
||||
*/
|
||||
public interface IDynamicSearchResourceProvider extends IResourceProvider {
|
||||
|
||||
List<RuntimeSearchParam> getSearchParameters();
|
||||
|
||||
}
|
|
@ -47,15 +47,23 @@ public class IncomingRequestAddressStrategy implements IServerAddressStrategy {
|
|||
requestPath = requestPath.substring(1);
|
||||
}
|
||||
|
||||
int startOfPath = requestUrl.indexOf("//");
|
||||
if (startOfPath != -1 && (startOfPath + 2) < requestUrl.length()) {
|
||||
startOfPath = requestUrl.indexOf("/", startOfPath + 2);
|
||||
}
|
||||
if (startOfPath == -1) {
|
||||
startOfPath = 0;
|
||||
}
|
||||
|
||||
int contextIndex;
|
||||
if (servletPath.length() == 0) {
|
||||
if (requestPath.length() == 0) {
|
||||
contextIndex = requestUrl.length();
|
||||
} else {
|
||||
contextIndex = requestUrl.indexOf(requestPath);
|
||||
contextIndex = requestUrl.indexOf(requestPath, startOfPath);
|
||||
}
|
||||
} else {
|
||||
contextIndex = requestUrl.indexOf(servletPath);
|
||||
contextIndex = requestUrl.indexOf(servletPath, startOfPath);
|
||||
}
|
||||
|
||||
String fhirServerBase;
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.param.BaseAndListParam;
|
||||
import ca.uhn.fhir.rest.param.CompositeAndListParam;
|
||||
import ca.uhn.fhir.rest.param.CompositeOrListParam;
|
||||
import ca.uhn.fhir.rest.param.DateAndListParam;
|
||||
import ca.uhn.fhir.rest.param.DateOrListParam;
|
||||
import ca.uhn.fhir.rest.param.NumberAndListParam;
|
||||
import ca.uhn.fhir.rest.param.NumberOrListParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityAndListParam;
|
||||
import ca.uhn.fhir.rest.param.QuantityOrListParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceAndListParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
|
||||
public class SearchParameterMap extends HashMap<String, BaseAndListParam<?>> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public <A extends IQueryParameterType, B extends IQueryParameterType> void add(String theName, CompositeOrListParam<A, B> theCompositeOrListParam) {
|
||||
@SuppressWarnings("unchecked")
|
||||
CompositeAndListParam<A, B> andList = (CompositeAndListParam<A, B>) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new CompositeAndListParam<A, B>(theCompositeOrListParam.getLeftType(), theCompositeOrListParam.getRightType());
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theCompositeOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, DateOrListParam theOrListParam) {
|
||||
DateAndListParam andList = (DateAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new DateAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, NumberOrListParam theOrListParam) {
|
||||
NumberAndListParam andList = (NumberAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new NumberAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, TokenOrListParam theOrListParam) {
|
||||
TokenAndListParam andList = (TokenAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new TokenAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, StringOrListParam theOrListParam) {
|
||||
StringAndListParam andList = (StringAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new StringAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, QuantityOrListParam theOrListParam) {
|
||||
QuantityAndListParam andList = (QuantityAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new QuantityAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
public void add(String theName, ReferenceOrListParam theOrListParam) {
|
||||
ReferenceAndListParam andList = (ReferenceAndListParam) get(theName);
|
||||
if (andList == null) {
|
||||
andList = new ReferenceAndListParam();
|
||||
put(theName, andList);
|
||||
}
|
||||
andList.addValue(theOrListParam);
|
||||
}
|
||||
|
||||
// public void add(String theName, IQueryParameterOr<?> theOr) {
|
||||
// if (theOr == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// switch (theOr.getClass()) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if (!containsKey(theName)) {
|
||||
// put(theName, new ArrayList<List<? extends IQueryParameterType>>());
|
||||
// }
|
||||
//
|
||||
// StringAndListParam
|
||||
//
|
||||
// IQueryParameterAnd<IQueryParameterOr<?>> and = get(theName);
|
||||
// and.add(theOr);
|
||||
// }
|
||||
|
||||
}
|
|
@ -49,6 +49,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.annotation.Metadata;
|
||||
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.IParameter;
|
||||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.SearchParameter;
|
||||
|
@ -61,11 +62,10 @@ import ca.uhn.fhir.util.ExtensionConstants;
|
|||
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
|
||||
*
|
||||
* <p>
|
||||
* Note: This class is safe to extend, but it is important to note that the same instance of
|
||||
* {@link Conformance} is always returned unless {@link #setCache(boolean)} is called with a value
|
||||
* of <code>false</code>. This means that if you are adding anything to the returned
|
||||
* conformance instance on each call you should call <code>setCache(false)</code> in
|
||||
* your provider constructor.
|
||||
* Note: This class is safe to extend, but it is important to note that the same instance of {@link Conformance} is
|
||||
* always returned unless {@link #setCache(boolean)} is called with a value of <code>false</code>. This means that if
|
||||
* you are adding anything to the returned conformance instance on each call you should call
|
||||
* <code>setCache(false)</code> in your provider constructor.
|
||||
* </p>
|
||||
*/
|
||||
public class ServerConformanceProvider {
|
||||
|
@ -142,91 +142,9 @@ public class ServerConformanceProvider {
|
|||
}
|
||||
|
||||
if (nextMethodBinding instanceof SearchMethodBinding) {
|
||||
SearchMethodBinding searchMethodBinding = (SearchMethodBinding) nextMethodBinding;
|
||||
includes.addAll(searchMethodBinding.getIncludes());
|
||||
|
||||
List<IParameter> params = searchMethodBinding.getParameters();
|
||||
List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
|
||||
for (IParameter nextParameter : params) {
|
||||
if ((nextParameter instanceof SearchParameter)) {
|
||||
searchParameters.add((SearchParameter) nextParameter);
|
||||
}
|
||||
}
|
||||
Collections.sort(searchParameters, new Comparator<SearchParameter>() {
|
||||
@Override
|
||||
public int compare(SearchParameter theO1, SearchParameter theO2) {
|
||||
if (theO1.isRequired() == theO2.isRequired()) {
|
||||
return theO1.getName().compareTo(theO2.getName());
|
||||
}
|
||||
if (theO1.isRequired()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
if (searchParameters.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
boolean allOptional = searchParameters.get(0).isRequired() == false;
|
||||
|
||||
RestQuery query = null;
|
||||
if (!allOptional) {
|
||||
query = rest.addQuery();
|
||||
query.getDocumentation().setValue(searchMethodBinding.getDescription());
|
||||
query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
|
||||
for (String nextInclude : searchMethodBinding.getIncludes()) {
|
||||
query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude));
|
||||
}
|
||||
}
|
||||
|
||||
for (SearchParameter nextParameter : searchParameters) {
|
||||
|
||||
String nextParamName = nextParameter.getName();
|
||||
|
||||
// String chain = null;
|
||||
String nextParamUnchainedName = nextParamName;
|
||||
if (nextParamName.contains(".")) {
|
||||
// chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
|
||||
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
|
||||
}
|
||||
|
||||
String nextParamDescription = nextParameter.getDescription();
|
||||
|
||||
/*
|
||||
* If the parameter has no description, default to the one from the resource
|
||||
*/
|
||||
if (StringUtils.isBlank(nextParamDescription)) {
|
||||
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
|
||||
if (paramDef != null) {
|
||||
nextParamDescription = paramDef.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
RestResourceSearchParam param;
|
||||
if (query == null) {
|
||||
param = resource.addSearchParam();
|
||||
} else {
|
||||
param = query.addParameter();
|
||||
param.addUndeclaredExtension(false, ExtensionConstants.PARAM_IS_REQUIRED, new BooleanDt(nextParameter.isRequired()));
|
||||
}
|
||||
|
||||
param.setName(nextParamName);
|
||||
// if (StringUtils.isNotBlank(chain)) {
|
||||
// param.addChain(chain);
|
||||
// }
|
||||
param.setDocumentation(nextParamDescription);
|
||||
param.setType(nextParameter.getParamType());
|
||||
for (Class<? extends IResource> nextTarget : nextParameter.getDeclaredTypes()) {
|
||||
RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget);
|
||||
if (targetDef != null) {
|
||||
ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName());
|
||||
if (code != null) {
|
||||
param.addTarget(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding);
|
||||
} else if (nextMethodBinding instanceof DynamicSearchMethodBinding) {
|
||||
handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding);
|
||||
}
|
||||
|
||||
Collections.sort(resource.getOperation(), new Comparator<RestResourceOperation>() {
|
||||
|
@ -259,6 +177,149 @@ public class ServerConformanceProvider {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void handleSearchMethodBinding(Rest rest, RestResource resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes, SearchMethodBinding searchMethodBinding) {
|
||||
includes.addAll(searchMethodBinding.getIncludes());
|
||||
|
||||
List<IParameter> params = searchMethodBinding.getParameters();
|
||||
List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
|
||||
for (IParameter nextParameter : params) {
|
||||
if ((nextParameter instanceof SearchParameter)) {
|
||||
searchParameters.add((SearchParameter) nextParameter);
|
||||
}
|
||||
}
|
||||
sortSearchParameters(searchParameters);
|
||||
if (!searchParameters.isEmpty()) {
|
||||
boolean allOptional = searchParameters.get(0).isRequired() == false;
|
||||
|
||||
RestQuery query = null;
|
||||
if (!allOptional) {
|
||||
query = rest.addQuery();
|
||||
query.getDocumentation().setValue(searchMethodBinding.getDescription());
|
||||
query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
|
||||
for (String nextInclude : searchMethodBinding.getIncludes()) {
|
||||
query.addUndeclaredExtension(false, ExtensionConstants.QUERY_ALLOWED_INCLUDE, new StringDt(nextInclude));
|
||||
}
|
||||
}
|
||||
|
||||
for (SearchParameter nextParameter : searchParameters) {
|
||||
|
||||
String nextParamName = nextParameter.getName();
|
||||
|
||||
// String chain = null;
|
||||
String nextParamUnchainedName = nextParamName;
|
||||
if (nextParamName.contains(".")) {
|
||||
// chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
|
||||
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
|
||||
}
|
||||
|
||||
String nextParamDescription = nextParameter.getDescription();
|
||||
|
||||
/*
|
||||
* If the parameter has no description, default to the one from the resource
|
||||
*/
|
||||
if (StringUtils.isBlank(nextParamDescription)) {
|
||||
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
|
||||
if (paramDef != null) {
|
||||
nextParamDescription = paramDef.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
RestResourceSearchParam param;
|
||||
if (query == null) {
|
||||
param = resource.addSearchParam();
|
||||
} else {
|
||||
param = query.addParameter();
|
||||
param.addUndeclaredExtension(false, ExtensionConstants.PARAM_IS_REQUIRED, new BooleanDt(nextParameter.isRequired()));
|
||||
}
|
||||
|
||||
param.setName(nextParamName);
|
||||
// if (StringUtils.isNotBlank(chain)) {
|
||||
// param.addChain(chain);
|
||||
// }
|
||||
param.setDocumentation(nextParamDescription);
|
||||
param.setType(nextParameter.getParamType());
|
||||
for (Class<? extends IResource> nextTarget : nextParameter.getDeclaredTypes()) {
|
||||
RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget);
|
||||
if (targetDef != null) {
|
||||
ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName());
|
||||
if (code != null) {
|
||||
param.addTarget(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDynamicSearchMethodBinding(RestResource resource, RuntimeResourceDefinition def, TreeSet<String> includes, DynamicSearchMethodBinding searchMethodBinding) {
|
||||
includes.addAll(searchMethodBinding.getIncludes());
|
||||
|
||||
List<RuntimeSearchParam> searchParameters = new ArrayList<RuntimeSearchParam>();
|
||||
searchParameters.addAll(searchMethodBinding.getSearchParams());
|
||||
sortRuntimeSearchParameters(searchParameters);
|
||||
|
||||
if (!searchParameters.isEmpty()) {
|
||||
|
||||
for (RuntimeSearchParam nextParameter : searchParameters) {
|
||||
|
||||
String nextParamName = nextParameter.getName();
|
||||
|
||||
// String chain = null;
|
||||
String nextParamUnchainedName = nextParamName;
|
||||
if (nextParamName.contains(".")) {
|
||||
// chain = nextParamName.substring(nextParamName.indexOf('.') + 1);
|
||||
nextParamUnchainedName = nextParamName.substring(0, nextParamName.indexOf('.'));
|
||||
}
|
||||
|
||||
String nextParamDescription = nextParameter.getDescription();
|
||||
|
||||
/*
|
||||
* If the parameter has no description, default to the one from the resource
|
||||
*/
|
||||
if (StringUtils.isBlank(nextParamDescription)) {
|
||||
RuntimeSearchParam paramDef = def.getSearchParam(nextParamUnchainedName);
|
||||
if (paramDef != null) {
|
||||
nextParamDescription = paramDef.getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
RestResourceSearchParam param;
|
||||
param = resource.addSearchParam();
|
||||
|
||||
param.setName(nextParamName);
|
||||
// if (StringUtils.isNotBlank(chain)) {
|
||||
// param.addChain(chain);
|
||||
// }
|
||||
param.setDocumentation(nextParamDescription);
|
||||
param.setType(nextParameter.getParamType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sortRuntimeSearchParameters(List<RuntimeSearchParam> searchParameters) {
|
||||
Collections.sort(searchParameters, new Comparator<RuntimeSearchParam>() {
|
||||
@Override
|
||||
public int compare(RuntimeSearchParam theO1, RuntimeSearchParam theO2) {
|
||||
return theO1.getName().compareTo(theO2.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sortSearchParameters(List<SearchParameter> searchParameters) {
|
||||
Collections.sort(searchParameters, new Comparator<SearchParameter>() {
|
||||
@Override
|
||||
public int compare(SearchParameter theO1, SearchParameter theO2) {
|
||||
if (theO1.isRequired() == theO2.isRequired()) {
|
||||
return theO1.getName().compareTo(theO2.getName());
|
||||
}
|
||||
if (theO1.isRequired()) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache property (default is true). If set to true, the same response will be returned for each
|
||||
* invocation.
|
||||
|
|
|
@ -1,4 +1,25 @@
|
|||
package ca.uhn.fhir.util;
|
||||
package ca.uhn.fhir.util;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
|
|
@ -39,6 +39,14 @@
|
|||
|
||||
<section name="Announcements">
|
||||
|
||||
<p>
|
||||
<b>Sep 8, 2014 - HAPI FHIR 0.6 Released</b> - A new build has been uploaded,
|
||||
containing a number of new features and bug fixes. See the changelog
|
||||
for a complete list of what has changed.
|
||||
<br/>
|
||||
- <a href="mailto:jamesagnew@users.sourceforge.net">James Agnew</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>July 30, 2014 - HAPI FHIR 0.5 Released</b> - HAPI 0.5 has now been released.
|
||||
This is surprisingly soon after the last release (and probably not a
|
||||
|
|
|
@ -82,7 +82,8 @@ public class DefaultThymeleafNarrativeGeneratorTest {
|
|||
enc.setType(EncounterTypeEnum.ANNUAL_DIABETES_MELLITUS_SCREENING);
|
||||
|
||||
String title = gen.generateTitle(enc);
|
||||
assertEquals("1234567 / ADMS / ambulatory / Tue Jan 02 11:11:00 EST 2001 - ?", title);
|
||||
title = title.replaceAll("00 [A-Z]+ 2001", "00 TZ 2001"); // account for whatever time zone
|
||||
assertEquals("1234567 / ADMS / ambulatory / Tue Jan 02 11:11:00 TZ 2001 - ?", title);
|
||||
ourLog.info(title);
|
||||
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ import ca.uhn.fhir.model.dstu.resource.ListResource;
|
|||
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.Profile;
|
||||
import ca.uhn.fhir.model.dstu.resource.Query;
|
||||
import ca.uhn.fhir.model.dstu.resource.Specimen;
|
||||
import ca.uhn.fhir.model.dstu.resource.ValueSet;
|
||||
|
@ -67,6 +68,16 @@ public class XmlParserTest {
|
|||
private static FhirContext ourCtx;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserTest.class);
|
||||
|
||||
@Test
|
||||
public void testEncodeProfile() {
|
||||
|
||||
Profile p = new Profile();
|
||||
p.getStructureFirstRep().getElementFirstRep().getDefinition().getBinding().setReference(new ResourceReferenceDt("ValudSet/123"));
|
||||
|
||||
String encoded = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
ourLog.info(encoded);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeBinaryResource() {
|
||||
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.apache.commons.lang.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.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.resource.Conformance;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.param.StringAndListParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
public class DynamicSearchTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = new FhirContext();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DynamicSearchTest.class);
|
||||
private static int ourPort;
|
||||
|
||||
private static Server ourServer;
|
||||
|
||||
private static SearchParameterMap ourLastSearchParams;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastSearchParams = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchOneStringParam() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=param1value");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
|
||||
assertEquals(1, ourLastSearchParams.size());
|
||||
StringAndListParam andList =(StringAndListParam) ourLastSearchParams.get("param1");
|
||||
assertEquals(1,andList.getValuesAsQueryTokens().size());
|
||||
StringOrListParam orList = andList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals(1,orList.getValuesAsQueryTokens().size());
|
||||
StringParam param1 = orList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals("param1value", param1.getValue());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchOneStringParamWithOr() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=param1value,param1value2");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
|
||||
assertEquals(1, ourLastSearchParams.size());
|
||||
StringAndListParam andList =(StringAndListParam) ourLastSearchParams.get("param1");
|
||||
assertEquals(1,andList.getValuesAsQueryTokens().size());
|
||||
StringOrListParam orList = andList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals(2,orList.getValuesAsQueryTokens().size());
|
||||
StringParam param1 = orList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals("param1value", param1.getValue());
|
||||
StringParam param1b = orList.getValuesAsQueryTokens().get(1);
|
||||
assertEquals("param1value2", param1b.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchOneStringParamWithAnd() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?param1=param1value¶m1=param1value2");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
|
||||
assertEquals(1, ourLastSearchParams.size());
|
||||
StringAndListParam andList =(StringAndListParam) ourLastSearchParams.get("param1");
|
||||
assertEquals(2,andList.getValuesAsQueryTokens().size());
|
||||
StringOrListParam orList = andList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals(1,orList.getValuesAsQueryTokens().size());
|
||||
StringParam param1 = orList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals("param1value", param1.getValue());
|
||||
|
||||
orList = andList.getValuesAsQueryTokens().get(1);
|
||||
assertEquals(1,orList.getValuesAsQueryTokens().size());
|
||||
StringParam param1b = orList.getValuesAsQueryTokens().get(0);
|
||||
assertEquals("param1value2", param1b.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConformance() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/metadata?_pretty=true");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Conformance conf = ourCtx.newXmlParser().parseResource(Conformance.class,responseContent);
|
||||
|
||||
ourLog.info(responseContent);
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer();
|
||||
servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||
|
||||
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 IDynamicSearchResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RuntimeSearchParam> getSearchParameters() {
|
||||
ArrayList<RuntimeSearchParam> retVal = new ArrayList<RuntimeSearchParam>();
|
||||
retVal.add(new RuntimeSearchParam("param1", "This is the first parameter", "Patient.param1", SearchParamTypeEnum.STRING));
|
||||
retVal.add(new RuntimeSearchParam("param2", "This is the second parameter", "Patient.param2", SearchParamTypeEnum.DATE));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Search(dynamic=true)
|
||||
public List<Patient> search(SearchParameterMap theSearchParams) {
|
||||
ourLastSearchParams = theSearchParams;
|
||||
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1");
|
||||
patient.addIdentifier("system", "fooCompartment");
|
||||
retVal.add(patient);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class IncomingRequestAddressStrategyTest {
|
||||
|
||||
@Test
|
||||
public void testAwsUrl() {
|
||||
|
||||
HttpServletRequest req = mock(HttpServletRequest.class);
|
||||
when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
|
||||
when(req.getServletPath()).thenReturn("/fhir");
|
||||
when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
|
||||
when(req.getContextPath()).thenReturn("/FhirStorm");
|
||||
|
||||
IncomingRequestAddressStrategy incomingRequestAddressStrategy = new IncomingRequestAddressStrategy();
|
||||
String actual = incomingRequestAddressStrategy.determineServerBase(req);
|
||||
assertEquals("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir", actual);
|
||||
}
|
||||
|
||||
/*
|
||||
IncomingRequestAddressStrategy15:29:02.876 [http-bio-8080-exec-3] TRACE c.uhn.fhir.rest.server.RestfulServer -
|
||||
Request FullPath: /FhirStorm/fhir/Patient/_search
|
||||
15:29:02.876 [http-bio-8080-exec-3] TRACE c.uhn.fhir.rest.server.RestfulServer -
|
||||
Servlet Path: /fhir
|
||||
15:29:02.876 [http-bio-8080-exec-3] TRACE c.uhn.fhir.rest.server.RestfulServer -
|
||||
Request Url: http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search
|
||||
15:29:02.876 [http-bio-8080-exec-3] TRACE c.uhn.fhir.rest.server.RestfulServer -
|
||||
Context Path: /FhirStorm
|
||||
*/
|
||||
}
|
|
@ -39,6 +39,7 @@ 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.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.StringOrListParam;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
@ -105,6 +106,21 @@ public class SearchTest {
|
|||
assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithOrList() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?findPatientWithOrList=aaa,bbb");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
|
||||
Patient p = bundle.getResources(Patient.class).get(0);
|
||||
assertEquals("aaa", p.getIdentifier().get(0).getValue().getValue());
|
||||
assertEquals("bbb", p.getIdentifier().get(1).getValue().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchByPost() throws Exception {
|
||||
HttpPost filePost = new HttpPost("http://localhost:" + ourPort + "/Patient/_search");
|
||||
|
@ -295,6 +311,20 @@ public class SearchTest {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> findPatientWithOrList(@RequiredParam(name = "findPatientWithOrList") StringOrListParam theParam) {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("1");
|
||||
for (StringParam next : theParam.getValuesAsQueryTokens()) {
|
||||
patient.addIdentifier("system", next.getValue());
|
||||
}
|
||||
retVal.add(patient);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Search(queryName = "findWithLinks")
|
||||
public List<Patient> findWithLinks() {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
|
|
@ -109,17 +109,14 @@ public abstract class BaseFhirDao implements IDao {
|
|||
private EntityManager myEntityManager;
|
||||
private List<IDaoListener> myListeners = new ArrayList<IDaoListener>();
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myPlatformTransactionManager;
|
||||
|
||||
@Autowired
|
||||
private List<IFhirResourceDao<?>> myResourceDaos;
|
||||
|
||||
private Map<Class<? extends IResource>, IFhirResourceDao<?>> myResourceTypeToDao;
|
||||
|
||||
protected void notifyWriteCompleted() {
|
||||
for (IDaoListener next : myListeners) {
|
||||
next.writeCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
public FhirContext getContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
@ -293,6 +290,18 @@ public abstract class BaseFhirDao implements IDao {
|
|||
}
|
||||
}
|
||||
|
||||
protected void createForcedIdIfNeeded(ResourceTable entity, IdDt id) {
|
||||
if (id.isEmpty() == false && id.hasIdPart()) {
|
||||
if (isValidPid(id)) {
|
||||
return;
|
||||
}
|
||||
ForcedId fid = new ForcedId();
|
||||
fid.setForcedId(id.getIdPart());
|
||||
fid.setResource(entity);
|
||||
entity.setForcedId(fid);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<ResourceLink> extractResourceLinks(ResourceTable theEntity, IResource theResource) {
|
||||
ArrayList<ResourceLink> retVal = new ArrayList<ResourceLink>();
|
||||
|
||||
|
@ -829,9 +838,6 @@ public abstract class BaseFhirDao implements IDao {
|
|||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myPlatformTransactionManager;
|
||||
|
||||
protected IBundleProvider history(String theResourceName, Long theId, Date theSince) {
|
||||
final List<HistoryTuple> tuples = new ArrayList<HistoryTuple>();
|
||||
|
||||
|
@ -905,6 +911,17 @@ public abstract class BaseFhirDao implements IDao {
|
|||
};
|
||||
}
|
||||
|
||||
protected boolean isValidPid(IdDt theId) {
|
||||
String idPart = theId.getIdPart();
|
||||
for (int i = 0; i < idPart.length(); i++) {
|
||||
char nextChar = idPart.charAt(i);
|
||||
if (nextChar < '0' || nextChar > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected List<IResource> loadResourcesById(Set<IdDt> theIncludePids) {
|
||||
Set<Long> pids = new HashSet<Long>();
|
||||
for (IdDt next : theIncludePids) {
|
||||
|
@ -943,6 +960,12 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return new String(out).toUpperCase();
|
||||
}
|
||||
|
||||
protected void notifyWriteCompleted() {
|
||||
for (IDaoListener next : myListeners) {
|
||||
next.writeCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
|
||||
|
||||
if (theEntity.getPublished().isEmpty()) {
|
||||
|
@ -1002,48 +1025,11 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
protected void createForcedIdIfNeeded(ResourceTable entity, IdDt id) {
|
||||
if (id.isEmpty() == false && id.hasIdPart()) {
|
||||
if (isValidPid(id)) {
|
||||
return;
|
||||
}
|
||||
ForcedId fid = new ForcedId();
|
||||
fid.setForcedId(id.getIdPart());
|
||||
fid.setResource(entity);
|
||||
entity.setForcedId(fid);
|
||||
}
|
||||
}
|
||||
|
||||
protected IResource toResource(BaseHasResource theEntity) {
|
||||
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
|
||||
return toResource(type.getImplementingClass(), theEntity);
|
||||
}
|
||||
|
||||
protected Long translateForcedIdToPid(IdDt theId) {
|
||||
if (isValidPid(theId)) {
|
||||
return theId.getIdPartAsLong();
|
||||
} else {
|
||||
TypedQuery<ForcedId> q = myEntityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class);
|
||||
q.setParameter("ID", theId.getIdPart());
|
||||
try {
|
||||
return q.getSingleResult().getResourcePid();
|
||||
} catch (NoResultException e) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isValidPid(IdDt theId) {
|
||||
String idPart = theId.getIdPart();
|
||||
for (int i = 0; i < idPart.length(); i++) {
|
||||
char nextChar = idPart.charAt(i);
|
||||
if (nextChar < '0' || nextChar > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected <T extends IResource> T toResource(Class<T> theResourceType, BaseHasResource theEntity) {
|
||||
String resourceText = null;
|
||||
switch (theEntity.getEncoding()) {
|
||||
|
@ -1095,6 +1081,20 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return myContext.getResourceDefinition(theResource).getName();
|
||||
}
|
||||
|
||||
protected Long translateForcedIdToPid(IdDt theId) {
|
||||
if (isValidPid(theId)) {
|
||||
return theId.getIdPartAsLong();
|
||||
} else {
|
||||
TypedQuery<ForcedId> q = myEntityManager.createNamedQuery("Q_GET_FORCED_ID", ForcedId.class);
|
||||
q.setParameter("ID", theId.getIdPart());
|
||||
try {
|
||||
return q.getSingleResult().getResourcePid();
|
||||
} catch (NoResultException e) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, boolean theDelete) {
|
||||
if (entity.getPublished() == null) {
|
||||
entity.setPublished(new Date());
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
<wb-module deploy-name="hapi-fhir-jpaserver-uhnfhirtest">
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-resources/tinder"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-sources/tinder"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
|
||||
<<<<<<< HEAD
|
||||
<dependent-module archiveName="hapi-fhir-jpaserver-base-0.6.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module archiveName="hapi-fhir-base-0.6.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6/hapi-fhir-testpage-overlay-0.6.war?unpackFolder=target/m2e-wtp/overlays&includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
=======
|
||||
<dependent-module archiveName="hapi-fhir-jpaserver-base-0.7-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module archiveName="hapi-fhir-base-0.7-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.7-SNAPSHOT/hapi-fhir-testpage-overlay-0.7-SNAPSHOT.war?unpackFolder=target/m2e-wtp/overlays&includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
>>>>>>> cdd4b137fb40dd72f895c2bcb644d6e668e1015b
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<property name="context-root" value="hapi-fhir-jpaserver"/>
|
||||
<property name="java-output-path" value="/hapi-fhir-jpaserver-uhnfhirtest/target/classes"/>
|
||||
</wb-module>
|
||||
</project-modules>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
|
||||
<wb-module deploy-name="restful-server-example">
|
||||
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
|
||||
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
|
||||
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
|
||||
<<<<<<< HEAD
|
||||
<dependent-module archiveName="hapi-fhir-base-0.6.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
=======
|
||||
<dependent-module archiveName="hapi-fhir-base-0.7-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
>>>>>>> cdd4b137fb40dd72f895c2bcb644d6e668e1015b
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6/hapi-fhir-testpage-overlay-0.6.war?unpackFolder=target/m2e-wtp/overlays&includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<property name="context-root" value="restful-server-example"/>
|
||||
<property name="java-output-path" value="/restful-server-example/target/classes"/>
|
||||
</wb-module>
|
||||
</project-modules>
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<groupId>ca.uhn.hapi.example</groupId>
|
||||
<artifactId>restful-server-example</artifactId>
|
||||
<version>0.7</version>
|
||||
<version>0.7-SNAPSHOT</version>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<name>HAPI FHIR Sample RESTful Server</name>
|
||||
|
|
Loading…
Reference in New Issue