Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
James Agnew 2014-09-16 12:06:42 -04:00
commit af64f1b9e1
36 changed files with 1495 additions and 222 deletions

View File

@ -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>

View File

@ -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

View File

@ -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&lt;Patient&gt; 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&lt;IResource&gt;
</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. &lt;xhtml:div xmlns:xhtml="..."&gt;...&lt;/xhtml:div&gt;)
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&lt;? extends IResource&gt;)
</action>
<action type="add">
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
"&amp;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>

View File

@ -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);

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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
}

View File

@ -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;

View File

@ -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();

View File

@ -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) {

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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();
}

View File

@ -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;

View File

@ -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);
// }
}

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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() {

View File

@ -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&param1=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;
}
}
}

View File

@ -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
*/
}

View File

@ -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>();

View File

@ -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());

View File

@ -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&amp;includes=**/**&amp;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&amp;includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
>>>>>>> cdd4b137fb40dd72f895c2bcb644d6e668e1015b
<dependency-type>consumes</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;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>

View File

@ -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&amp;includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;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>

View File

@ -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>