Enable validation ($validate) operation in DSTU2 style

This commit is contained in:
James Agnew 2015-06-04 10:56:13 -04:00
parent e5b402cb14
commit 9b97fb0e97
71 changed files with 1859 additions and 981 deletions

View File

@ -59,6 +59,7 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.CompositeParam;
@ -836,7 +837,9 @@ public abstract MethodOutcome updateSomePatient(@IdParam IdDt theId, @ResourcePa
//START SNIPPET: validate
@Validate
public MethodOutcome validatePatient(@ResourceParam Patient thePatient) {
public MethodOutcome validatePatient(@ResourceParam Patient thePatient,
@Validate.Mode ValidationModeEnum theMode,
@Validate.Profile String theProfile) {
// Actually do our validation: The UnprocessableEntityException
// results in an HTTP 422, which is appropriate for business rule failure

View File

@ -66,6 +66,16 @@ public class BuiltJarIT {
}
for (File file : files) {
if (file.getName().endsWith("sources.jar")) {
continue;
}
if (file.getName().endsWith("javadoc.jar")) {
continue;
}
if (file.getName().contains("original.jar")) {
continue;
}
ourLog.info("Testing file: {}", file);
ZipFile zip = new ZipFile(file);

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* Adapter implementation with NOP implementations of all {@link IParserErrorHandler} methods.
*/

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* Error handler
*/

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* The default error handler, which logs issues but does not abort parsing
*

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 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%
*/
/**
* Parser error handler which throws a {@link DataFormatException} any time an
* issue is found while parsing.

View File

@ -26,6 +26,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
/**
@ -36,6 +37,12 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
* Validate is used to accept a resource, and test whether it would be acceptable for
* storing (e.g. using an update or create method)
* </p>
* <p>
* <b>FHIR Version Note:</b> The validate operation was defined as a type operation in DSTU1
* using a URL syntax like <code>http://example.com/Patient/_validate</code>. In DSTU2, validation
* has been switched to being an extended operation using a URL syntax like
* <code>http://example.com/Patient/$validate</code>, with a n
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
@ -50,4 +57,24 @@ public @interface Validate {
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
Class<? extends IResource> type() default IResource.class;
/**
* Validation mode parameter annotation for the validation mode parameter (only supported
* in FHIR DSTU2+). Parameter must be of type {@link ValidationModeEnum}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.PARAMETER)
@interface Mode {
// nothing
}
/**
* Validation mode parameter annotation for the validation URI parameter (only supported
* in FHIR DSTU2+). Parameter must be of type {@link String}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.PARAMETER)
@interface Profile {
// nothing
}
}

View File

@ -0,0 +1,28 @@
package ca.uhn.fhir.rest.api;
import org.hl7.fhir.instance.model.api.IBase;
/**
* Validation mode parameter for the $validate operation (DSTU2+ only)
*/
public enum ValidationModeEnum implements IBase {
/**
* The server checks the content, and then checks that the content would be acceptable as a create (e.g. that the content would not validate any uniqueness constraints)
*/
CREATE,
/**
* The server checks the content, and then checks that it would accept it as an update against the nominated specific resource (e.g. that there are no changes to immutable fields the server does not allow to change, and checking version integrity if appropriate)
*/
UPDATE,
/**
* The server ignores the content, and checks that the nominated resource is allowed to be deleted (e.g. checking referential integrity rules)
*/
DELETE;
@Override
public boolean isEmpty() {
return false;
}
}

View File

@ -20,7 +20,8 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.Reader;
@ -116,7 +117,7 @@ import ca.uhn.fhir.rest.method.ReadMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
import ca.uhn.fhir.rest.method.TransactionMethodBinding;
import ca.uhn.fhir.rest.method.ValidateMethodBinding;
import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu1;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
@ -507,7 +508,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public MethodOutcome validate(IResource theResource) {
BaseHttpClientInvocation invocation = ValidateMethodBinding.createValidateInvocation(theResource, null, myContext);
BaseHttpClientInvocation invocation = ValidateMethodBindingDstu1.createValidateInvocation(theResource, null, myContext);
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
}

View File

@ -160,7 +160,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
Object[] params = createParametersForServerRequest(theRequest, null);
params[myIdParamIndex] = theRequest.getId();
@ -199,7 +199,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (theRequest.getRequestType() != RequestTypeEnum.POST) {
return false;
}

View File

@ -38,6 +38,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
@ -110,7 +111,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return parser;
}
protected IParser createAppropriateParserForParsingServerRequest(Request theRequest) {
protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) {
String contentTypeHeader = theRequest.getServletRequest().getHeader("content-type");
EncodingEnum encoding;
if (isBlank(contentTypeHeader)) {
@ -131,7 +132,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return parser;
}
protected Object[] createParametersForServerRequest(Request theRequest, byte[] theRequestContents) {
protected Object[] createParametersForServerRequest(RequestDetails theRequest, byte[] theRequestContents) {
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
@ -241,11 +242,11 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
public abstract RestfulOperationSystemEnum getSystemOperationType();
public abstract boolean incomingServerRequestMatchesMethod(Request theRequest);
public abstract boolean incomingServerRequestMatchesMethod(RequestDetails theRequest);
public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException;
public abstract void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException;
protected Object invokeServerMethod(Object[] theMethodParams) {
try {
@ -262,7 +263,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
}
}
protected byte[] loadRequestContents(Request theRequest) throws IOException {
protected byte[] loadRequestContents(RequestDetails theRequest) throws IOException {
byte[] requestContents = IOUtils.toByteArray(theRequest.getServletRequest().getInputStream());
return requestContents;
}
@ -305,6 +306,30 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
myParameters = theParameters;
}
protected IBundleProvider toResourceList(Object response) throws InternalErrorException {
if (response == null) {
return BundleProviders.newEmptyList();
} else if (response instanceof IBundleProvider) {
return (IBundleProvider) response;
} else if (response instanceof IResource) {
return BundleProviders.newList((IResource) response);
} else if (response instanceof Collection) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
for (Object next : ((Collection<?>) response)) {
retVal.add((IBaseResource) next);
}
return BundleProviders.newList(retVal);
} else if (response instanceof MethodOutcome) {
IBaseResource retVal = ((MethodOutcome) response).getOperationOutcome();
if (retVal == null) {
retVal = getContext().getResourceDefinition("OperationOutcome").newInstance();
}
return BundleProviders.newList(retVal);
} else {
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
}
}
@SuppressWarnings("unchecked")
public static BaseMethodBinding<?> bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
Read read = theMethod.getAnnotation(Read.class);
@ -438,7 +463,11 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
} else if (history != null) {
return new HistoryMethodBinding(theMethod, theContext, theProvider);
} else if (validate != null) {
return new ValidateMethodBinding(theMethod, theContext, theProvider);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
return new ValidateMethodBindingDstu1(theMethod, theContext, theProvider);
} else {
return new ValidateMethodBindingDstu2(returnType, returnTypeFromRp, theMethod, theContext, theProvider, validate);
}
} else if (getTags != null) {
return new GetTagsMethodBinding(theMethod, theContext, theProvider, getTags);
} else if (addTags != null) {
@ -491,24 +520,6 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
return theType.getCanonicalName();
}
protected static IBundleProvider toResourceList(Object response) throws InternalErrorException {
if (response == null) {
return BundleProviders.newEmptyList();
} else if (response instanceof IBundleProvider) {
return (IBundleProvider) response;
} else if (response instanceof IResource) {
return BundleProviders.newList((IResource) response);
} else if (response instanceof Collection) {
List<IBaseResource> retVal = new ArrayList<IBaseResource>();
for (Object next : ((Collection<?>) response)) {
retVal.add((IBaseResource) next);
}
return BundleProviders.newList(retVal);
} else {
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
}
}
private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
if (theReturnType == null) {
return false;

View File

@ -67,7 +67,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
}
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation) {
private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation) {
StringBuilder b = new StringBuilder();
b.append(theRequest.getFhirServerBase());
b.append('/');
@ -84,7 +84,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
theResponse.addHeader(headerLocation, b.toString());
}
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
protected abstract void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams);
/**
* Subclasses may override to allow a void method return type, which is allowable for some methods (e.g. delete)
@ -101,7 +101,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
protected abstract String getMatchingOperation();
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
Set<RequestTypeEnum> allowableRequestTypes = provideAllowableRequestTypes();
RequestTypeEnum requestType = theRequest.getRequestType();
if (!allowableRequestTypes.contains(requestType)) {
@ -120,8 +120,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
@Override
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
switch (theResponseStatusCode) {
case Constants.STATUS_HTTP_200_OK:
case Constants.STATUS_HTTP_201_CREATED:
@ -138,7 +137,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
byte[] requestContents = loadRequestContents(theRequest);
// if (requestContainsResource()) {
// requestContents = parseIncomingServerResource(theRequest);
@ -243,7 +242,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
// getMethod().in
}
private void addContentLocationHeaders(Request theRequest, HttpServletResponse servletResponse, MethodOutcome response) {
private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response) {
if (response != null && response.getId() != null) {
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION);
addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_CONTENT_LOCATION);
@ -256,7 +255,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
protected abstract Set<RequestTypeEnum> provideAllowableRequestTypes();
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncodingNotNull, HttpServletResponse theResponse, Request theRequest)
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncodingNotNull, HttpServletResponse theResponse, RequestDetails theRequest)
throws IOException {
theResponse.setStatus(theE.getStatusCode());

View File

@ -80,7 +80,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
}
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
if (myIdParamIndex != null) {
theParams[myIdParamIndex] = theRequest.getId();
}

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.Reader;
@ -45,8 +45,10 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.server.Constants;
@ -112,6 +114,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
} else if (IBundleProvider.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
} else {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
+ theMethod.getDeclaringClass().getCanonicalName());
@ -151,7 +155,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
public abstract ReturnTypeEnum getReturnType();
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException {
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) {
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode);
switch (getReturnType()) {
@ -191,6 +195,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
case BUNDLE_PROVIDER:
throw new IllegalStateException("Return type of " + IBundleProvider.class.getSimpleName() + " is not supported in clients");
default:
break;
}
break;
}
@ -213,6 +219,13 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
return resource;
case BUNDLE_PROVIDER:
throw new IllegalStateException("Return type of " + IBundleProvider.class.getSimpleName() + " is not supported in clients");
case BUNDLE_RESOURCE:
// TODO: we should support this
throw new IllegalStateException("Return type of " + IBundleProvider.class.getSimpleName() + " is not yet supported in clients");
case METHOD_OUTCOME:
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome((BaseOperationOutcome) resource);
return retVal;
}
break;
}
@ -224,7 +237,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
public abstract Object invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
@Override
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
@ -351,7 +364,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
return;
}
}
RestfulServerUtils.streamResponseAsResource(theServer, response, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode,
RestfulServerUtils.streamResponseAsResource(theServer, response, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode,
Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), isAddContentLocationHeader());
}
@ -395,7 +408,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
public enum MethodReturnTypeEnum {
BUNDLE, BUNDLE_PROVIDER, BUNDLE_RESOURCE, LIST_OF_RESOURCES, RESOURCE
BUNDLE, BUNDLE_PROVIDER, BUNDLE_RESOURCE, LIST_OF_RESOURCES, RESOURCE, METHOD_OUTCOME
}
public enum ReturnTypeEnum {

View File

@ -51,7 +51,7 @@ class ConditionalParamBinder implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
if (myOperationType == RestfulOperationTypeEnum.CREATE) {
String retVal = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_EXIST);

View File

@ -75,7 +75,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (theRequest.getRequestType() == RequestTypeEnum.OPTIONS) {
return true;
}

View File

@ -54,7 +54,7 @@ public class CountParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_COUNT);
if (sinceParams != null) {
if (sinceParams.length > 0) {

View File

@ -143,7 +143,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
}
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
theParams[myIdParameterIndex] = theRequest.getId();
}

View File

@ -74,7 +74,7 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi
List<IParameter> retVal = new ArrayList<IParameter>(super.getParameters());
for (RuntimeSearchParam next : mySearchParameters) {
// TODO: what is this?
}
return retVal;
@ -108,7 +108,7 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DynamicSearchMethodBinding.class);
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (!theRequest.getResourceName().equals(getResourceName())) {
ourLog.trace("Method {} doesn't match because resource name {} != {}", getMethod().getName(), theRequest.getResourceName(), getResourceName());
return false;

View File

@ -44,6 +44,7 @@ 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.param.UriOrListParam;
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
import ca.uhn.fhir.rest.server.SearchParameterMap;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -66,7 +67,7 @@ public class DynamicSearchParameter implements IParameter {
@SuppressWarnings("unchecked")
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
SearchParameterMap retVal = new SearchParameterMap();
for (String next : theRequest.getParameters().keySet()) {
@ -133,6 +134,11 @@ public class DynamicSearchParameter implements IParameter {
tokenOrListParam.setValuesAsQueryTokens(paramList);
retVal.add(next, tokenOrListParam);
break;
case URI:
UriOrListParam uriOrListParam = new UriOrListParam();
uriOrListParam.setValuesAsQueryTokens(paramList);
retVal.add(next, uriOrListParam);
break;
}
}
}

View File

@ -82,7 +82,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
}
@Override
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws BaseServerResponseException {
if (theResponseStatusCode == Constants.STATUS_HTTP_200_OK) {
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode);
TagList retVal = parser.parseTagList(theResponseReader);
@ -148,7 +148,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest) throws BaseServerResponseException, IOException {
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
Object[] params = createParametersForServerRequest(theRequest, null);
if (myIdParamIndex != null) {
@ -188,7 +188,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (theRequest.getRequestType()!=RequestTypeEnum.GET) {
return false;
}

View File

@ -114,7 +114,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
// ObjectUtils.equals is replaced by a JDK7 method..
@SuppressWarnings("deprecation")
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
return false;
}

View File

@ -48,7 +48,7 @@ public interface IParameter {
* @param theMethodBinding TODO
* @return Returns the argument object as it will be passed to the {@link IResourceProvider} method.
*/
Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException;
Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException;
void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType);

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Count;
@ -64,9 +65,12 @@ import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Sort;
import ca.uhn.fhir.rest.annotation.TagListParam;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.method.OperationParameter.IConverter;
import ca.uhn.fhir.rest.param.CollectionBinder;
import ca.uhn.fhir.rest.param.DateAndListParam;
import ca.uhn.fhir.rest.param.NumberAndListParam;
@ -436,7 +440,37 @@ public class MethodUtil {
param = new ConditionalParamBinder(theRestfulOperationTypeEnum);
} else if (nextAnnotation instanceof OperationParam) {
Operation op = theMethod.getAnnotation(Operation.class);
param = new OperationParameter(op.name(), (OperationParam) nextAnnotation);
param = new OperationParameter(op.name(), ((OperationParam) nextAnnotation).name());
} else if (nextAnnotation instanceof Validate.Mode) {
if (parameterType.equals(ValidationModeEnum.class) == false) {
throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
}
param = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE).setConverter(new IConverter() {
@Override
public Object outgoingClient(Object theObject) {
return new StringDt(((ValidationModeEnum)theObject).name().toLowerCase());
}
@Override
public Object incomingServer(Object theObject) {
return ValidationModeEnum.valueOf(theObject.toString().toUpperCase());
}
});
} else if (nextAnnotation instanceof Validate.Profile) {
if (parameterType.equals(String.class) == false) {
throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
}
param = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE).setConverter(new IConverter() {
@Override
public Object outgoingClient(Object theObject) {
return new StringDt(theObject.toString());
}
@Override
public Object incomingServer(Object theObject) {
return theObject.toString();
}
});
} else {
continue;
}

View File

@ -47,7 +47,7 @@ class NarrativeModeParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
String val = theRequest.getServletRequest().getParameter(Constants.PARAM_NARRATIVE);
if (val != null) {
try {

View File

@ -39,7 +39,7 @@ class NullParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
// nothing
return null;
}

View File

@ -59,23 +59,28 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private final Integer myIdParamIndex;
private final String myName;
private final ReturnTypeEnum myReturnType;
private final OtherOperationTypeEnum myOtherOperatiopnType;
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
Operation theAnnotation) {
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type());
}
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName,
Class<? extends IBaseResource> theOperationType) {
super(theReturnResourceType, theMethod, theContext, theProvider);
myHttpGetPermitted = theAnnotation.idempotent();
myHttpGetPermitted = theIdempotent;
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
String name = theAnnotation.name();
if (isBlank(name)) {
if (isBlank(theOperationName)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName()
+ " but this annotation has no name defined");
}
if (name.startsWith("$") == false) {
name = "$" + name;
if (theOperationName.startsWith("$") == false) {
theOperationName = "$" + theOperationName;
}
myName = name;
myName = theOperationName;
if (theContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU1)) {
throw new ConfigurationException("@" + Operation.class.getSimpleName() + " methods are not supported on servers for FHIR version " + theContext.getVersion().getVersion().name());
@ -84,8 +89,8 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
if (theReturnTypeFromRp != null) {
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
} else {
if (Modifier.isAbstract(theAnnotation.type().getModifiers()) == false) {
setResourceName(theContext.getResourceDefinition(theAnnotation.type()).getName());
if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
} else {
setResourceName(null);
}
@ -102,6 +107,18 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myReturnType = ReturnTypeEnum.RESOURCE;
}
if (getResourceName() == null) {
myOtherOperatiopnType = OtherOperationTypeEnum.EXTENDED_OPERATION_SERVER;
} else if (myIdParamIndex == null) {
myOtherOperatiopnType = OtherOperationTypeEnum.EXTENDED_OPERATION_TYPE;
} else {
myOtherOperatiopnType = OtherOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
}
}
@Override
public OtherOperationTypeEnum getOtherOperationType() {
return myOtherOperatiopnType;
}
@Override
@ -125,7 +142,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (getResourceName() == null) {
if (isNotBlank(theRequest.getResourceName())) {
return false;

View File

@ -41,43 +41,25 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.param.CollectionBinder;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
class OperationParameter implements IParameter {
private final String myName;
private Class<?> myParameterType;
private IConverter myConverter;
@SuppressWarnings("rawtypes")
private Class<? extends Collection> myInnerCollectionType;
private final String myName;
private final String myOperationName;
private Class<?> myParameterType;
OperationParameter(String theOperationName, OperationParam theAnnotation) {
OperationParameter(String theOperationName, String theParameterName) {
myOperationName = theOperationName;
myName = theAnnotation.name();
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource)
throws InternalErrorException {
assert theTargetResource != null;
if (theSourceClientArgument == null) {
return;
}
RuntimeResourceDefinition def = theContext.getResourceDefinition(theTargetResource);
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
addClientParameter(theContext, theSourceClientArgument, theTargetResource, paramChild, paramChildElem);
myName = theParameterName;
}
private void addClientParameter(FhirContext theContext, Object theSourceClientArgument, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem) {
@ -110,9 +92,43 @@ class OperationParameter implements IParameter {
return parameter;
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
myParameterType = theParameterType;
if (theInnerCollectionType != null) {
myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, myName);
}
}
public OperationParameter setConverter(IConverter theConverter) {
myConverter = theConverter;
return this;
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource)
throws InternalErrorException {
assert theTargetResource != null;
Object sourceClientArgument = theSourceClientArgument;
if (sourceClientArgument == null) {
return;
}
if (myConverter != null) {
sourceClientArgument = myConverter.outgoingClient(sourceClientArgument);
}
RuntimeResourceDefinition def = theContext.getResourceDefinition(theTargetResource);
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
addClientParameter(theContext, sourceClientArgument, theTargetResource, paramChild, paramChildElem);
}
@SuppressWarnings("unchecked")
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
List<Object> matchingParamValues = new ArrayList<Object>();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
@ -199,10 +215,13 @@ class OperationParameter implements IParameter {
}
private void tryToAddValues(List<IBase> theParamValues, List<Object> theMatchingParamValues) {
for (IBase nextValue : theParamValues) {
for (Object nextValue : theParamValues) {
if (nextValue == null) {
continue;
}
if (myConverter != null) {
nextValue = myConverter.incomingServer(nextValue);
}
if (!myParameterType.isAssignableFrom(nextValue.getClass())) {
throw new InvalidRequestException("Request has parameter " + myName + " of type " + nextValue.getClass().getSimpleName() + " but method expects type "
+ myParameterType.getSimpleName());
@ -211,12 +230,12 @@ class OperationParameter implements IParameter {
}
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
myParameterType = theParameterType;
if (theInnerCollectionType != null) {
myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, myName);
}
public interface IConverter {
Object incomingServer(Object theObject);
Object outgoingClient(Object theObject);
}
}

View File

@ -30,7 +30,22 @@ public enum OtherOperationTypeEnum {
GET_TAGS("get-tags"),
GET_PAGE("get-page");
GET_PAGE("get-page"),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_SERVER("extended-operation-server"),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_TYPE("extended-operation-type"),
/**
* E.g. $everything, $validate, etc.
*/
EXTENDED_OPERATION_INSTANCE("extended-operation-instance");
private String myCode;

View File

@ -110,7 +110,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (!theRequest.getResourceName().equals(getResourceName())) {
return false;
}
@ -186,7 +186,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
case RESOURCE:
return resource;
case BUNDLE_PROVIDER:
return new SimpleBundleProvider((IResource) resource);
return new SimpleBundleProvider(resource);
}
throw new IllegalStateException("" + getMethodReturnType()); // should not happen
@ -203,7 +203,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
IBundleProvider retVal = toResourceList(response);
if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {
String ifNoneMatch = ((Request)theRequest).getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC);
String ifNoneMatch = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC);
if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) {
List<IBaseResource> responseResources = retVal.getResources(0, 1);
IBaseResource responseResource = responseResources.get(0);

View File

@ -1,158 +0,0 @@
package ca.uhn.fhir.rest.method;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
/**
* This class is internal to HAPI - Use with caution as methods may change in future versions of the library
*/
public class Request extends RequestDetails {
private String myFhirServerBase;
private String myOperation;
private String myRequestPath;
private boolean myRespondGzip;
private String mySecondaryOperation;
private HttpServletRequest myServletRequest;
private HttpServletResponse myServletResponse;
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
public String getFhirServerBase() {
return myFhirServerBase;
}
public String getOperation() {
return myOperation;
}
/**
* The part of the request URL that comes after the server base.
* <p>
* Will not contain a leading '/'
* </p>
*/
public String getRequestPath() {
return myRequestPath;
}
public String getSecondaryOperation() {
return mySecondaryOperation;
}
public HttpServletRequest getServletRequest() {
return myServletRequest;
}
public HttpServletResponse getServletResponse() {
return myServletResponse;
}
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
return myUnqualifiedToQualifiedNames;
}
public boolean isRespondGzip() {
return myRespondGzip;
}
public void setFhirServerBase(String theFhirServerBase) {
myFhirServerBase = theFhirServerBase;
}
public void setOperation(String theOperation) {
myOperation = theOperation;
}
@Override
public void setParameters(Map<String, String[]> theParams) {
super.setParameters(theParams);
for (String next : theParams.keySet()) {
for (int i = 0; i < next.length(); i++) {
char nextChar = next.charAt(i);
if (nextChar == ':' || nextChar == '.') {
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = new HashMap<String, List<String>>();
}
String unqualified = next.substring(0, i);
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
if (list == null) {
list = new ArrayList<String>(4);
myUnqualifiedToQualifiedNames.put(unqualified, list);
}
list.add(next);
break;
}
}
}
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = Collections.emptyMap();
}
}
public void setRequestPath(String theRequestPath) {
assert theRequestPath.length() == 0 || theRequestPath.charAt(0) != '/';
myRequestPath = theRequestPath;
}
public void setRespondGzip(boolean theRespondGzip) {
myRespondGzip = theRespondGzip;
}
public void setSecondaryOperation(String theSecondaryOperation) {
mySecondaryOperation = theSecondaryOperation;
}
public void setServletRequest(HttpServletRequest theRequest) {
myServletRequest = theRequest;
}
public void setServletResponse(HttpServletResponse theServletResponse) {
myServletResponse = theServletResponse;
}
public static Request withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set<String> theParamNames) {
Request retVal = new Request();
retVal.setResourceName(theResourceName);
retVal.setRequestType(theRequestType);
Map<String, String[]> paramNames = new HashMap<String, String[]>();
for (String next : theParamNames) {
paramNames.put(next, new String[0]);
}
retVal.setParameters(paramNames);
return retVal;
}
}

View File

@ -20,7 +20,15 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
@ -32,14 +40,22 @@ public class RequestDetails {
private String myCompartmentName;
private String myCompleteUrl;
private String myFhirServerBase;
private IdDt myId;
private String myOperation;
private OtherOperationTypeEnum myOtherOperationType;
private Map<String, String[]> myParameters;
private String myRequestPath;
private RequestTypeEnum myRequestType;
private String myResourceName;
private RestfulOperationTypeEnum myResourceOperationType;
private boolean myRespondGzip;
private String mySecondaryOperation;
private RestfulServer myServer;
private HttpServletRequest myServletRequest;
private HttpServletResponse myServletResponse;
private RestfulOperationSystemEnum mySystemOperationType;
private Map<String, List<String>> myUnqualifiedToQualifiedNames;
public String getCompartmentName() {
return myCompartmentName;
@ -49,10 +65,18 @@ public class RequestDetails {
return myCompleteUrl;
}
public String getFhirServerBase() {
return myFhirServerBase;
}
public IdDt getId() {
return myId;
}
public String getOperation() {
return myOperation;
}
public OtherOperationTypeEnum getOtherOperationType() {
return myOtherOperationType;
}
@ -61,6 +85,16 @@ public class RequestDetails {
return myParameters;
}
/**
* The part of the request URL that comes after the server base.
* <p>
* Will not contain a leading '/'
* </p>
*/
public String getRequestPath() {
return myRequestPath;
}
public RequestTypeEnum getRequestType() {
return myRequestType;
}
@ -73,14 +107,34 @@ public class RequestDetails {
return myResourceOperationType;
}
public String getSecondaryOperation() {
return mySecondaryOperation;
}
public RestfulServer getServer() {
return myServer;
}
public HttpServletRequest getServletRequest() {
return myServletRequest;
}
public HttpServletResponse getServletResponse() {
return myServletResponse;
}
public RestfulOperationSystemEnum getSystemOperationType() {
return mySystemOperationType;
}
public Map<String, List<String>> getUnqualifiedToQualifiedNames() {
return myUnqualifiedToQualifiedNames;
}
public boolean isRespondGzip() {
return myRespondGzip;
}
public void setCompartmentName(String theCompartmentName) {
myCompartmentName = theCompartmentName;
}
@ -89,16 +143,53 @@ public class RequestDetails {
myCompleteUrl = theCompleteUrl;
}
public void setFhirServerBase(String theFhirServerBase) {
myFhirServerBase = theFhirServerBase;
}
public void setId(IdDt theId) {
myId = theId;
}
public void setOperation(String theOperation) {
myOperation = theOperation;
}
public void setOtherOperationType(OtherOperationTypeEnum theOtherOperationType) {
myOtherOperationType = theOtherOperationType;
}
public void setParameters(Map<String, String[]> theParams) {
myParameters = theParams;
for (String next : theParams.keySet()) {
for (int i = 0; i < next.length(); i++) {
char nextChar = next.charAt(i);
if (nextChar == ':' || nextChar == '.') {
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = new HashMap<String, List<String>>();
}
String unqualified = next.substring(0, i);
List<String> list = myUnqualifiedToQualifiedNames.get(unqualified);
if (list == null) {
list = new ArrayList<String>(4);
myUnqualifiedToQualifiedNames.put(unqualified, list);
}
list.add(next);
break;
}
}
}
if (myUnqualifiedToQualifiedNames == null) {
myUnqualifiedToQualifiedNames = Collections.emptyMap();
}
}
public void setRequestPath(String theRequestPath) {
assert theRequestPath.length() == 0 || theRequestPath.charAt(0) != '/';
myRequestPath = theRequestPath;
}
public void setRequestType(RequestTypeEnum theRequestType) {
@ -113,12 +204,40 @@ public class RequestDetails {
myResourceOperationType = theResourceOperationType;
}
public void setRespondGzip(boolean theRespondGzip) {
myRespondGzip = theRespondGzip;
}
public void setSecondaryOperation(String theSecondaryOperation) {
mySecondaryOperation = theSecondaryOperation;
}
public void setServer(RestfulServer theServer) {
myServer = theServer;
}
public void setServletRequest(HttpServletRequest theRequest) {
myServletRequest = theRequest;
}
public void setServletResponse(HttpServletResponse theServletResponse) {
myServletResponse = theServletResponse;
}
public void setSystemOperationType(RestfulOperationSystemEnum theSystemOperationType) {
mySystemOperationType = theSystemOperationType;
}
public static RequestDetails withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set<String> theParamNames) {
RequestDetails retVal = new RequestDetails();
retVal.setResourceName(theResourceName);
retVal.setRequestType(theRequestType);
Map<String, String[]> paramNames = new HashMap<String, String[]>();
for (String next : theParamNames) {
paramNames.put(next, new String[0]);
}
retVal.setParameters(paramNames);
return retVal;
}
}

View File

@ -148,7 +148,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (!theRequest.getResourceName().equals(getResourceName())) {
ourLog.trace("Method {} doesn't match because resource name {} != {}", getMethod().getName(), theRequest.getResourceName(), getResourceName());
return false;

View File

@ -43,7 +43,7 @@ class ServerBaseParamBinder implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest.getFhirServerBase();
}

View File

@ -43,7 +43,7 @@ class ServletRequestParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest.getServletRequest();
}

View File

@ -43,7 +43,7 @@ class ServletResponseParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest.getServletResponse();
}

View File

@ -54,7 +54,7 @@ class SinceParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_SINCE);
if (sinceParams != null) {
if (sinceParams.length > 0) {

View File

@ -65,7 +65,7 @@ public class SortParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT)) {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_ASC)) {
if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_DESC)) {

View File

@ -97,7 +97,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
if (theRequest.getRequestType() != RequestTypeEnum.POST) {
return false;
}

View File

@ -59,7 +59,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
/*
* We are being a bit lenient here, since technically the client is supposed to include the version in the
* Content-Location header, but we allow it in the PUT URL as well..
@ -122,7 +122,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
/*
* @Override public boolean incomingServerRequestMatchesMethod(Request theRequest) { if
* @Override public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { if
* (super.incomingServerRequestMatchesMethod(theRequest)) { if (myVersionIdParameterIndex != null) { if
* (theRequest.getVersionId() == null) { return false; } } else { if (theRequest.getVersionId() != null) { return
* false; } } return true; } else { return false; } }

View File

@ -34,11 +34,11 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
public class ValidateMethodBindingDstu1 extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;
public ValidateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
public ValidateMethodBindingDstu1(Method theMethod, FhirContext theContext, Object theProvider) {
super(theMethod, theContext, Validate.class, theProvider);
myIdParameterIndex = MethodUtil.findIdParameterIndex(theMethod);
@ -55,7 +55,7 @@ public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWith
}
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) {
if (myIdParameterIndex != null) {
theParams[myIdParameterIndex] = theRequest.getId();
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class ValidateMethodBindingDstu2 extends OperationMethodBinding {
public ValidateMethodBindingDstu2(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
Validate theAnnotation) {
super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type());
List<IParameter> newParams = new ArrayList<IParameter>();
int idx = 0;
for (IParameter next : getParameters()) {
if (next instanceof ResourceParameter) {
if (IBaseResource.class.isAssignableFrom(((ResourceParameter) next).getResourceType())) {
Class<?> parameterType = theMethod.getParameterTypes()[idx];
if (String.class.equals(parameterType) || EncodingEnum.class.equals(parameterType)) {
newParams.add(next);
} else {
OperationParameter parameter = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_RESOURCE);
parameter.initializeTypes(theMethod, null, null, parameterType);
newParams.add(parameter);
}
} else {
newParams.add(next);
}
} else {
newParams.add(next);
}
idx++;
}
setParameters(newParams);
}
}

View File

@ -34,7 +34,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
@ -130,7 +129,7 @@ public abstract class BaseQueryParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
List<QualifiedParamList> paramList = new ArrayList<QualifiedParamList>();
String name = getName();

View File

@ -20,7 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -49,8 +50,7 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -103,7 +103,7 @@ public class ResourceParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
switch (myMode) {
case BODY:
try {
@ -138,11 +138,11 @@ public class ResourceParameter implements IParameter {
return requestReader;
}
static Reader createRequestReader(Request theRequest, byte[] theRequestContents) {
static Reader createRequestReader(RequestDetails theRequest, byte[] theRequestContents) {
return createRequestReader(theRequestContents, determineRequestCharset(theRequest));
}
static Charset determineRequestCharset(Request theRequest) {
static Charset determineRequestCharset(RequestDetails theRequest) {
String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
Charset charset = null;
@ -156,7 +156,7 @@ public class ResourceParameter implements IParameter {
return charset;
}
public static IBaseResource loadResourceFromRequest(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
public static IBaseResource loadResourceFromRequest(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
FhirContext ctx = theRequest.getServer().getFhirContext();
final Charset charset = determineRequestCharset(theRequest);

View File

@ -40,7 +40,7 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -98,7 +98,7 @@ public class TransactionParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
// TODO: don't use a default encoding, just fail!
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);

View File

@ -43,6 +43,7 @@ public class Constants {
public static final String CT_TEXT_WITH_UTF8 = CT_TEXT + "; charset=UTF-8";
public static final String CT_XML = "application/xml";
public static final String ENCODING_GZIP = "gzip";
public static final String EXTOP_VALIDATE = "$validate";
public static final String FORMAT_JSON = "json";
public static final Set<String> FORMAT_VAL_JSON;
public static final Map<String, EncodingEnum> FORMAT_VAL_TO_ENCODING;
@ -91,13 +92,13 @@ public class Constants {
public static final String PARAM_FORMAT = "_format";
public static final String PARAM_HISTORY = "_history";
public static final String PARAM_INCLUDE = "_include";
public static final String PARAM_REVINCLUDE = "_revinclude";
public static final String PARAM_NARRATIVE = "_narrative";
public static final String PARAM_PAGINGACTION = "_getpages";
public static final String PARAM_PAGINGOFFSET = "_getpagesoffset";
public static final String PARAM_PRETTY = "_pretty";
public static final String PARAM_PRETTY_VALUE_TRUE = "true";
public static final String PARAM_QUERY = "_query";
public static final String PARAM_REVINCLUDE = "_revinclude";
public static final String PARAM_SEARCH = "_search";
public static final String PARAM_SINCE = "_since";
public static final String PARAM_SORT = "_sort";
@ -106,6 +107,8 @@ public class Constants {
public static final String PARAM_TAGS = "_tags";
public static final String PARAM_VALIDATE = "_validate";
public static final String PARAMQUALIFIER_MISSING = ":missing";
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
public static final String PARAMQUALIFIER_MISSING_TRUE = "true";
public static final String PARAMQUALIFIER_STRING_EXACT = ":exact";
public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text";
public static final int STATUS_HTTP_200_OK = 200;
@ -125,8 +128,9 @@ public class Constants {
public static final int STATUS_HTTP_501_NOT_IMPLEMENTED = 501;
public static final String URL_TOKEN_HISTORY = "_history";
public static final String URL_TOKEN_METADATA = "metadata";
public static final String PARAMQUALIFIER_MISSING_TRUE = "true";
public static final String PARAMQUALIFIER_MISSING_FALSE = "false";
public static final String EXTOP_VALIDATE_MODE = "mode";
public static final String EXTOP_VALIDATE_PROFILE = "profile";
public static final String EXTOP_VALIDATE_RESOURCE = "resource";
static {
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();

View File

@ -24,7 +24,7 @@ import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
/**
* Created by dsotnikov on 2/25/2014.
@ -44,7 +44,7 @@ public class ResourceBinding {
this.methods = methods;
}
public BaseMethodBinding<?> getMethod(Request theRequest) throws Exception {
public BaseMethodBinding<?> getMethod(RequestDetails theRequest) {
if (null == methods) {
ourLog.warn("No methods exist for resource: {}", resourceName);
return null;

View File

@ -27,7 +27,16 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
@ -37,17 +46,13 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -55,11 +60,9 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.OtherOperationTypeEnum;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
@ -426,7 +429,7 @@ public class RestfulServer extends HttpServlet {
return myServerVersion;
}
private void handlePagingRequest(Request theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException {
private void handlePagingRequest(RequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException {
IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction);
if (resultList == null) {
ourLog.info("Client requested unknown paging ID[{}]", thePagingAction);
@ -496,7 +499,7 @@ public class RestfulServer extends HttpServlet {
return;
}
}
RestfulServerUtils.streamResponseAsResource(this, theResponse, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
}
}
@ -511,7 +514,7 @@ public class RestfulServer extends HttpServlet {
String fhirServerBase = null;
boolean requestIsBrowser = requestIsBrowser(theRequest);
Request requestDetails = new Request();
RequestDetails requestDetails = new RequestDetails();
requestDetails.setServer(this);
try {
@ -816,6 +819,7 @@ public class RestfulServer extends HttpServlet {
* (which extends {@link ServletException}), as this is a flag to the servlet container that the servlet
* is not usable.
*/
@SuppressWarnings("unused")
protected void initialize() throws ServletException {
// nothing by default
}

View File

@ -40,9 +40,9 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
@ -55,24 +55,264 @@ import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
if (isNotBlank(countString)) {
public static void addProfileToBundleEntry(FhirContext theContext, IBaseResource theResource, String theServerBase) {
if (theResource instanceof IResource) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get((IResource) theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put((IResource) theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
}
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
try {
count = Integer.parseInt(countString);
} catch (NumberFormatException e) {
ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
b.append(URLEncoder.encode(theSearchId, "UTF-8"));
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
if (theResponseEncoding != null) {
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
}
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
if (theIncludes != null) {
for (Include nextInclude : theIncludes) {
if (isNotBlank(nextInclude.getValue())) {
b.append('&');
b.append(Constants.PARAM_INCLUDE);
b.append('=');
b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8"));
}
}
return count;
}
return b.toString();
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen
}
}
public static RestfulServer.NarrativeModeEnum determineNarrativeMode(RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] narrative = requestParams.get(Constants.PARAM_NARRATIVE);
RestfulServer.NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
try {
narrativeMode = RestfulServer.NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
} catch (IllegalArgumentException e) {
ourLog.debug("Invalid {} parameger: {}", Constants.PARAM_NARRATIVE, narrative[0]);
narrativeMode = null;
}
}
if (narrativeMode == null) {
narrativeMode = RestfulServer.NarrativeModeEnum.NORMAL;
}
return narrativeMode;
}
public static EncodingEnum determineRequestEncoding(RequestDetails theReq) {
EncodingEnum retVal = determineRequestEncodingNoDefault(theReq);
if (retVal != null) {
return retVal;
}
return EncodingEnum.XML;
}
public static EncodingEnum determineRequestEncodingNoDefault(RequestDetails theReq) {
EncodingEnum retVal = null;
Enumeration<String> acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
if (acceptValues != null) {
while (acceptValues.hasMoreElements() && retVal == null) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
break;
}
}
}
}
}
return retVal;
}
public static EncodingEnum determineResponseEncodingNoDefault(HttpServletRequest theReq) {
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return null;
}
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
EncodingEnum retVal = determineResponseEncodingNoDefault(theReq);
if (retVal == null) {
retVal = theServer.getDefaultResponseEncoding();
}
return retVal;
}
public static Integer extractCountParameter(HttpServletRequest theRequest) {
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT);
}
public static IParser getNewParser(FhirContext theContext, EncodingEnum theResponseEncoding, boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
parser = theContext.newJsonParser();
break;
case XML:
default:
parser = theContext.newXmlParser();
break;
}
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == RestfulServer.NarrativeModeEnum.SUPPRESS);
}
static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
Writer writer;
if (theRespondGzip) {
theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8");
} else {
writer = theHttpResponse.getWriter();
}
return writer;
}
public static boolean prettyPrintResponse(RestfulServer theServer, RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.get(Constants.PARAM_PRETTY);
boolean prettyPrint;
if (pretty != null && pretty.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) {
prettyPrint = true;
} else {
prettyPrint = false;
}
} else {
prettyPrint = theServer.isDefaultPrettyPrint();
Enumeration<String> acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue.contains("pretty=true")) {
prettyPrint = true;
}
}
}
}
return prettyPrint;
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, boolean theRequestIsBrowser) throws IOException {
assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding();
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
for (IResource next : bundle.toListOfResources()) {
writer.append(next.getText().getDiv().getValueAsString());
writer.append("<hr/>");
}
} else {
IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
parser.setServerBaseUrl(theServerBase);
parser.encodeBundleToWriter(bundle, writer);
}
} finally {
writer.close();
}
}
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
@ -168,258 +408,17 @@ public class RestfulServerUtils {
}
}
public static boolean prettyPrintResponse(RestfulServer theServer, RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.get(Constants.PARAM_PRETTY);
boolean prettyPrint;
if (pretty != null && pretty.length > 0) {
if (Constants.PARAM_PRETTY_VALUE_TRUE.equals(pretty[0])) {
prettyPrint = true;
} else {
prettyPrint = false;
}
} else {
prettyPrint = theServer.isDefaultPrettyPrint();
Enumeration<String> acceptValues = ((Request)theRequest).getServletRequest().getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue.contains("pretty=true")) {
prettyPrint = true;
}
}
}
}
return prettyPrint;
}
static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
Writer writer;
if (theRespondGzip) {
theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8");
} else {
writer = theHttpResponse.getWriter();
}
return writer;
}
public static EncodingEnum determineRequestEncoding(Request theReq) {
EncodingEnum retVal = determineRequestEncodingNoDefault(theReq);
if (retVal != null) {
return retVal;
}
return EncodingEnum.XML;
}
public static EncodingEnum determineRequestEncodingNoDefault(Request theReq) {
EncodingEnum retVal = null;
Enumeration<String> acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE);
if (acceptValues != null) {
while (acceptValues.hasMoreElements() && retVal == null) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
break;
}
}
}
}
}
return retVal;
}
public static String createPagingLink(Set<Include> theIncludes, String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
if (isNotBlank(countString)) {
try {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
b.append(Constants.PARAM_PAGINGACTION);
b.append('=');
b.append(URLEncoder.encode(theSearchId, "UTF-8"));
b.append('&');
b.append(Constants.PARAM_PAGINGOFFSET);
b.append('=');
b.append(theOffset);
b.append('&');
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
if (theResponseEncoding != null) {
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
}
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
if (theIncludes != null) {
for (Include nextInclude : theIncludes) {
if (isNotBlank(nextInclude.getValue())) {
b.append('&');
b.append(Constants.PARAM_INCLUDE);
b.append('=');
b.append(URLEncoder.encode(nextInclude.getValue(), "UTF-8"));
count = Integer.parseInt(countString);
} catch (NumberFormatException e) {
ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
}
}
}
return b.toString();
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported", e);// should not happen
}
}
public static void addProfileToBundleEntry(FhirContext theContext, IBaseResource theResource, String theServerBase) {
if (theResource instanceof IResource) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get((IResource) theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put((IResource) theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
}
public static RestfulServer.NarrativeModeEnum determineNarrativeMode(RequestDetails theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] narrative = requestParams.get(Constants.PARAM_NARRATIVE);
RestfulServer.NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
try {
narrativeMode = RestfulServer.NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
} catch (IllegalArgumentException e) {
ourLog.debug("Invalid {} parameger: {}", Constants.PARAM_NARRATIVE, narrative[0]);
narrativeMode = null;
}
}
if (narrativeMode == null) {
narrativeMode = RestfulServer.NarrativeModeEnum.NORMAL;
}
return narrativeMode;
}
/**
* Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's <code>"_format"</code> parameter and <code>"Accept:"</code> HTTP header.
*/
public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
EncodingEnum retVal = determineResponseEncodingNoDefault(theReq);
if (retVal == null) {
retVal = theServer.getDefaultResponseEncoding();
}
return retVal;
}
public static IParser getNewParser(FhirContext theContext, EncodingEnum theResponseEncoding, boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode) {
IParser parser;
switch (theResponseEncoding) {
case JSON:
parser = theContext.newJsonParser();
break;
case XML:
default:
parser = theContext.newXmlParser();
break;
}
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == RestfulServer.NarrativeModeEnum.SUPPRESS);
}
public static EncodingEnum determineResponseEncodingNoDefault(HttpServletRequest theReq) {
String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
String nextAcceptHeaderValue = acceptValues.nextElement();
if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) {
for (String nextPart : nextAcceptHeaderValue.split(",")) {
int scIdx = nextPart.indexOf(';');
if (scIdx == 0) {
continue;
}
if (scIdx != -1) {
nextPart = nextPart.substring(0, scIdx);
}
nextPart = nextPart.trim();
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextPart);
if (retVal != null) {
return retVal;
}
}
}
}
}
return null;
}
public static Integer extractCountParameter(HttpServletRequest theRequest) {
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT);
}
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, boolean theRequestIsBrowser) throws IOException {
assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding();
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
theHttpResponse.setContentType(Constants.CT_HTML);
} else {
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
for (IResource next : bundle.toListOfResources()) {
writer.append(next.getText().getDiv().getValueAsString());
writer.append("<hr/>");
}
} else {
IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
parser.setServerBaseUrl(theServerBase);
parser.encodeBundleToWriter(bundle, writer);
}
} finally {
writer.close();
}
return count;
}
// public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,

View File

@ -38,6 +38,8 @@ 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;
import ca.uhn.fhir.rest.param.UriAndListParam;
import ca.uhn.fhir.rest.param.UriOrListParam;
public class SearchParameterMap extends LinkedHashMap<String, BaseAndListParam<?>> {
@ -71,10 +73,19 @@ public class SearchParameterMap extends LinkedHashMap<String, BaseAndListParam<?
andList.addValue(theOrListParam);
}
public void add(String theName, TokenOrListParam theOrListParam) {
TokenAndListParam andList = (TokenAndListParam) get(theName);
public void add(String theName, QuantityOrListParam theOrListParam) {
QuantityAndListParam andList = (QuantityAndListParam) get(theName);
if (andList == null) {
andList = new TokenAndListParam();
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);
@ -89,19 +100,19 @@ public class SearchParameterMap extends LinkedHashMap<String, BaseAndListParam<?
andList.addValue(theOrListParam);
}
public void add(String theName, QuantityOrListParam theOrListParam) {
QuantityAndListParam andList = (QuantityAndListParam) get(theName);
public void add(String theName, TokenOrListParam theOrListParam) {
TokenAndListParam andList = (TokenAndListParam) get(theName);
if (andList == null) {
andList = new QuantityAndListParam();
andList = new TokenAndListParam();
put(theName, andList);
}
andList.addValue(theOrListParam);
}
public void add(String theName, ReferenceOrListParam theOrListParam) {
ReferenceAndListParam andList = (ReferenceAndListParam) get(theName);
public void add(String theName, UriOrListParam theOrListParam) {
UriAndListParam andList = (UriAndListParam) get(theName);
if (andList == null) {
andList = new ReferenceAndListParam();
andList = new UriAndListParam();
put(theName, andList);
}
andList.addValue(theOrListParam);

View File

@ -35,7 +35,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.RestfulServer;
@ -127,7 +126,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
}
boolean requestIsBrowser = RestfulServer.requestIsBrowser(theRequest);
String fhirServerBase = ((Request) theRequestDetails).getFhirServerBase();
String fhirServerBase = theRequestDetails.getFhirServerBase();
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser,
NarrativeModeEnum.NORMAL, statusCode, false, fhirServerBase, false);

View File

@ -35,6 +35,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
/**
@ -52,8 +54,12 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
* <td>The resource ID associated with this request, or the resource name if the request applies to a type but not an instance, or "" otherwise</td>
* </tr>
* <tr>
* <td>${operationName}</td>
* <td>If the request is an extended operation (e.g. "$validate") this value will be the operation name, or "" otherwise</td>
* </tr>
* <tr>
* <td>${operationType}</td>
* <td>A code indicating the operation type for this request, e.g. "read", "history-instance", etc.)</td>
* <td>A code indicating the operation type for this request, e.g. "read", "history-instance", "extended-operation-instance", etc.)</td>
* </tr>
* <tr>
* <td>${remoteAddr}</td>
@ -69,6 +75,10 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
* <td>The HTTP request parameters (or "")</td>
* </tr>
* <tr>
* <td>${responseEncodingNoDefault}</td>
* <td>The encoding format requested by the client via the _format parameter or the Accept header. Value will be "json" or "xml", or "" if the client did not explicitly request a format</td>
* </tr>
* <tr>
* <td>${servletPath}</td>
* <td>The part of thre requesting URL that corresponds to the particular Servlet being called (see {@link HttpServletRequest#getServletPath()})</td>
* </tr>
@ -125,6 +135,11 @@ public class LoggingInterceptor extends InterceptorAdapter {
@Override
public String lookup(String theKey) {
/*
* TODO: this method could be made more efficient through some sort of lookup map
*/
if ("operationType".equals(theKey)) {
if (myRequestDetails.getResourceOperationType() != null) {
return myRequestDetails.getResourceOperationType().getCode();
@ -136,6 +151,19 @@ public class LoggingInterceptor extends InterceptorAdapter {
return myRequestDetails.getOtherOperationType().getCode();
}
return "";
} else if ("operationName".equals(theKey)) {
if (myRequestDetails.getOtherOperationType() != null) {
switch (myRequestDetails.getOtherOperationType()) {
case EXTENDED_OPERATION_INSTANCE:
case EXTENDED_OPERATION_SERVER:
case EXTENDED_OPERATION_TYPE:
return myRequestDetails.getOperation();
default:
return "";
}
} else {
return "";
}
} else if ("id".equals(theKey)) {
if (myRequestDetails.getId() != null) {
return myRequestDetails.getId().getValue();
@ -175,7 +203,15 @@ public class LoggingInterceptor extends InterceptorAdapter {
return StringUtils.defaultString(val);
} else if (theKey.startsWith("remoteAddr")) {
return StringUtils.defaultString(myRequest.getRemoteAddr());
} else if (theKey.equals("responseEncodingNoDefault")) {
EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingNoDefault(myRequest);
if (encoding != null) {
return encoding.name();
} else {
return "";
}
}
return "!VAL!";
}
}

View File

@ -1,5 +1,25 @@
package org.hl7.fhir.instance.model.api;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IBaseConformance extends IBaseResource {

View File

@ -1,5 +1,25 @@
package org.hl7.fhir.instance.model.api;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IBaseXhtml extends IPrimitiveType<String> {

View File

@ -77,6 +77,19 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
<version>${phloc_schematron_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-commons</artifactId>
<version>${phloc_commons_version}</version>
<scope>test</scope>
</dependency>
<!-- For UCUM -->
<dependency>
<groupId>org.jscience</groupId>

View File

@ -24,9 +24,16 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome.Issue;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
@ -35,8 +42,14 @@ import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResourceProvider<T> {
@ -78,25 +91,6 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
}
@Update
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalUrlParam String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {
return getDao().update(theResource, theConditional);
} else {
theResource.setId(theId);
return getDao().update(theResource);
}
} catch (ResourceNotFoundException e) {
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
theResource.setId(theId);
return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
}
//@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
@ -141,4 +135,67 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
return parameters;
}
@Update
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalUrlParam String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {
return getDao().update(theResource, theConditional);
} else {
theResource.setId(theId);
return getDao().update(theResource);
}
} catch (ResourceNotFoundException e) {
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
theResource.setId(theId);
return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
}
@Validate
public MethodOutcome validate(@ResourceParam T theResource, @ResourceParam String theRawResource, @ResourceParam EncodingEnum theEncoding, @Validate.Mode ValidationModeEnum theMode,
@Validate.Profile String theProfile) {
final OperationOutcome oo = new OperationOutcome();
IParser parser = theEncoding.newParser(getContext());
parser.setParserErrorHandler(new IParserErrorHandler() {
@Override
public void unknownAttribute(IParseLocation theLocation, String theAttributeName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown attribute found: " + theAttributeName);
}
@Override
public void unknownElement(IParseLocation theLocation, String theElementName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown element found: " + theElementName);
}
});
FhirValidator validator = getContext().newValidator();
validator.setValidateAgainstStandardSchema(true);
validator.setValidateAgainstStandardSchematron(true);
ValidationResult result = validator.validateWithResult(theResource);
for (BaseIssue next : result.getOperationOutcome().getIssue()) {
oo.getIssue().add((Issue) next);
}
if (oo.getIssue().size() > 0) {
/*
* It is also possible to pass an OperationOutcome resource to the UnprocessableEntityException if you want to return a custom populated OperationOutcome. Otherwise, a simple one is
* created using the string supplied below.
*/
throw new UnprocessableEntityException("Validation failed", oo);
}
// This method returns a MethodOutcome object
MethodOutcome retVal = new MethodOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Validation succeeded");
retVal.setOperationOutcome(oo);
return retVal;
}
}

View File

@ -23,6 +23,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
@ -69,6 +70,7 @@ import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum;
import ca.uhn.fhir.model.dstu2.valueset.EncounterStateEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UnsignedIntDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
@ -91,59 +93,57 @@ public class ResourceProviderDstu2Test {
private static ClassPathXmlApplicationContext ourAppCtx;
private static IGenericClient ourClient;
private static DaoConfig ourDaoConfig;
private static FhirContext ourFhirCtx;
private static CloseableHttpClient ourHttpClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu2Test.class);
private static IFhirResourceDao<Organization> ourOrganizationDao;
private static int ourPort;
// private static IFhirResourceDao<Observation> ourObservationDao;
// private static IFhirResourceDao<Patient> ourPatientDao;
// private static IFhirResourceDao<Questionnaire> ourQuestionnaireDao;
private static Server ourServer;
private static IFhirResourceDao<Organization> ourOrganizationDao;
private static DaoConfig ourDaoConfig;
private static CloseableHttpClient ourHttpClient;
private static String ourServerBase;
private static int ourPort;
// private static JpaConformanceProvider ourConfProvider;
/**
* Test for issue #60
*/
private void delete(String theResourceType, String theParamName, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new StringClientParam(theParamName).matches().value(theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
@Test
public void testStoreUtf8Characters() throws Exception {
public void testCountParam() throws Exception {
// NB this does not get used- The paging provider has its own limits built in
ourDaoConfig.setHardSearchLimit(100);
List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i = 0; i < 100; i++) {
Organization org = new Organization();
org.setName("測試醫院");
org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01");
IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
// Read back directly from the DAO
{
Organization returned = ourOrganizationDao.read(orgId);
String val = ourFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned);
ourLog.info(val);
assertThat(val, containsString("<name value=\"測試醫院\"/>"));
}
// Read back through the HTTP API
{
Organization returned = ourClient.read(Organization.class, orgId);
String val = ourFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned);
ourLog.info(val);
assertThat(val, containsString("<name value=\"測試醫院\"/>"));
}
org.setName("rpdstu2_testCountParam_01");
resources.add(org);
}
ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute();
@Test
public void testCreateResourceWithNumericId() throws IOException {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"></Patient>";
Bundle found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).limitTo(10).execute();
assertEquals(100, found.getTotalResults().getValue().intValue());
assertEquals(10, found.getEntries().size());
HttpPost post = new HttpPost(ourServerBase + "/Patient/2");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).limitTo(999).execute();
assertEquals(100, found.getTotalResults().getValue().intValue());
assertEquals(50, found.getEntries().size());
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
}
@Test
@ -183,37 +183,56 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testUpdateResourceConditional() throws IOException {
String methodName = "testUpdateResourceConditional";
public void testCreateResourceWithNumericId() throws IOException {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"></Patient>";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = ourFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPost post = new HttpPost(ourServerBase + "/Patient?name=" + methodName);
HttpPost post = new HttpPost(ourServerBase + "/Patient/2");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
IdDt id;
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdDt(newIdString);
assertEquals(400, response.getStatusLine().getStatusCode());
} finally {
response.close();
}
}
HttpPut put = new HttpPut(ourServerBase + "/Patient?name=" + methodName);
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
IdDt newId = new IdDt(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue());
assertEquals(id.toVersionless(), newId.toVersionless()); // version shouldn't match for conditional update
assertNotEquals(id, newId);
} finally {
response.close();
}
@Test
public void testDeepChaining() {
delete("Location", Location.SP_NAME, "testDeepChainingL1");
delete("Location", Location.SP_NAME, "testDeepChainingL2");
deleteToken("Encounter", Encounter.SP_IDENTIFIER, "urn:foo", "testDeepChainingE1");
Location l1 = new Location();
l1.getNameElement().setValue("testDeepChainingL1");
IdDt l1id = ourClient.create().resource(l1).execute().getId();
Location l2 = new Location();
l2.getNameElement().setValue("testDeepChainingL2");
l2.getPartOf().setReference(l1id.toVersionless().toUnqualified());
IdDt l2id = ourClient.create().resource(l2).execute().getId();
Encounter e1 = new Encounter();
e1.addIdentifier().setSystem("urn:foo").setValue("testDeepChainingE1");
e1.getStatusElement().setValueAsEnum(EncounterStateEnum.IN_PROGRESS);
e1.getClassElementElement().setValueAsEnum(EncounterClassEnum.HOME);
ca.uhn.fhir.model.dstu2.resource.Encounter.Location location = e1.addLocation();
location.getLocation().setReference(l2id.toUnqualifiedVersionless());
location.setPeriod(new PeriodDt().setStartWithSecondsPrecision(new Date()).setEndWithSecondsPrecision(new Date()));
IdDt e1id = ourClient.create().resource(e1).execute().getId();
//@formatter:off
Bundle res = ourClient.search()
.forResource(Encounter.class)
.where(Encounter.IDENTIFIER.exactly().systemAndCode("urn:foo", "testDeepChainingE1"))
.include(Encounter.INCLUDE_LOCATION)
.include(Location.INCLUDE_PARTOF)
.execute();
//@formatter:on
assertEquals(3, res.size());
assertEquals(1, res.getResources(Encounter.class).size());
assertEquals(e1id.toUnqualifiedVersionless(), res.getResources(Encounter.class).get(0).getId().toUnqualifiedVersionless());
}
@ -325,184 +344,62 @@ public class ResourceProviderDstu2Test {
}
/**
* Test for issue #60
* See issue #52
*/
@Test
public void testReadAllInstancesOfType() throws Exception {
Patient pat;
public void testDiagnosticOrderResources() throws Exception {
IGenericClient client = ourClient;
pat = new Patient();
pat.addIdentifier().setSystem("urn:system").setValue("testReadAllInstancesOfType_01");
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size();
pat = new Patient();
pat.addIdentifier().setSystem("urn:system").setValue("testReadAllInstancesOfType_02");
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
DiagnosticOrder res = new DiagnosticOrder();
res.addIdentifier().setSystem("urn:foo").setValue("123");
client.create().resource(res).execute();
int newSize = client.search().forResource(DiagnosticOrder.class).execute().size();
assertEquals(1, newSize - initialSize);
{
Bundle returned = ourClient.search().forResource(Patient.class).encodedXml().execute();
assertThat(returned.size(), greaterThan(1));
assertEquals(BundleTypeEnum.SEARCHSET, returned.getType().getValueAsEnum());
}
{
Bundle returned = ourClient.search().forResource(Patient.class).encodedJson().execute();
assertThat(returned.size(), greaterThan(1));
}
}
/**
* See issue #52
*/
@Test
public void testSearchBundleDoesntIncludeTextElement() throws Exception {
HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json");
CloseableHttpResponse response = ourHttpClient.execute(read);
try {
String text = IOUtils.toString(response.getEntity().getContent());
ourLog.info(text);
assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
assertThat(text, not(containsString("\"text\",\"type\"")));
} finally {
response.close();
}
public void testDocumentManifestResources() throws Exception {
ourFhirCtx.getResourceDefinition(Practitioner.class);
ourFhirCtx.getResourceDefinition(ca.uhn.fhir.model.dstu.resource.DocumentManifest.class);
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DocumentManifest.class).execute().size();
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentmanifest.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(DocumentManifest.class).execute().size();
assertEquals(1, newSize - initialSize);
}
/**
* See issue #52
*/
@Test
public void testSearchWithInclude() throws Exception {
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude01");
IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
public void testDocumentReferenceResources() throws Exception {
IGenericClient client = ourClient;
Patient pat = new Patient();
pat.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude02");
pat.getManagingOrganization().setReference(orgId);
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
int initialSize = client.search().forResource(DocumentReference.class).execute().size();
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Patient.class)
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier("urn:system:rpdstu2","testSearchWithInclude02"))
.include(Patient.INCLUDE_ORGANIZATION)
.prettyPrint()
.execute();
//@formatter:on
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentreference.json"));
client.create().resource(resBody).execute();
assertEquals(2, found.size());
assertEquals(Patient.class, found.getEntries().get(0).getResource().getClass());
assertEquals(BundleEntrySearchModeEnum.MATCH, found.getEntries().get(0).getSearchMode().getValueAsEnum());
assertEquals(BundleEntrySearchModeEnum.MATCH, found.getEntries().get(0).getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE));
assertThat(found.getEntries().get(0).getResource().getText().getDiv().getValueAsString(), containsString("<table class=\"hapiPropertyTable"));
assertEquals(Organization.class, found.getEntries().get(1).getResource().getClass());
assertEquals(BundleEntrySearchModeEnum.INCLUDE, found.getEntries().get(1).getSearchMode().getValueAsEnum());
assertEquals(BundleEntrySearchModeEnum.INCLUDE, found.getEntries().get(1).getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE));
}
int newSize = client.search().forResource(DocumentReference.class).execute().size();
@Test
public void testSearchWithMissing() throws Exception {
ourLog.info("Starting testSearchWithMissing");
String methodName = "testSearchWithMissing";
assertEquals(1, newSize - initialSize);
List<IResource> resources = new ArrayList<IResource>();
for (int i = 0; i < 20; i++) {
Organization org = new Organization();
org.setName(methodName + "_0" + i);
resources.add(org);
}
ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute();
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01");
org.setName(methodName + "name");
IdDt orgNotMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless();
org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01");
IdDt orgMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless();
{
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Organization.class)
.where(Organization.NAME.isMissing(false))
.limitTo(100)
.prettyPrint()
.execute();
//@formatter:on
List<IdDt> list = toIdListUnqualifiedVersionless(found);
ourLog.info(methodName + ": " + list.toString());
assertThat("Wanted " + orgNotMissing + " but got: " + list, list, containsInRelativeOrder(orgNotMissing));
assertThat(list, not(containsInRelativeOrder(orgMissing)));
}
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Organization.class)
.where(Organization.NAME.isMissing(true))
.limitTo(100)
.prettyPrint()
.execute();
//@formatter:on
List<IdDt> list = toIdListUnqualifiedVersionless(found);
ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing);
assertThat(list, not(containsInRelativeOrder(orgNotMissing)));
assertThat("Wanted " + orgMissing + " but found: " + list, list, containsInRelativeOrder(orgMissing));
}
private List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
List<IdDt> list = new ArrayList<IdDt>();
for (BundleEntry next : found.getEntries()) {
list.add(next.getResource().getId().toUnqualifiedVersionless());
}
return list;
}
@Test
public void testEverythingOperation() throws Exception {
String methodName = "testEverythingOperation";
Organization org1 = new Organization();
org1.setName(methodName + "1");
IdDt orgId1 = ourClient.create().resource(org1).execute().getId();
Patient p = new Patient();
p.addName().addFamily(methodName);
p.getManagingOrganization().setReference(orgId1);
IdDt patientId = ourClient.create().resource(p).execute().getId();
Organization org2 = new Organization();
org2.setName(methodName + "1");
IdDt orgId2 = ourClient.create().resource(org2).execute().getId();
Device dev = new Device();
dev.setModel(methodName);
dev.getOwner().setReference(orgId2);
IdDt devId = ourClient.create().resource(dev).execute().getId();
Observation obs = new Observation();
obs.getSubject().setReference(patientId);
obs.getDevice().setReference(devId);
IdDt obsId = ourClient.create().resource(obs).execute().getId();
Encounter enc = new Encounter();
enc.getPatient().setReference(patientId);
IdDt encId = ourClient.create().resource(enc).execute().getId();
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
ca.uhn.fhir.model.dstu2.resource.Bundle b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
Set<IdDt> ids = new HashSet<IdDt>();
for (Entry next : b.getEntry()) {
ids.add(next.getResource().getId());
}
assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2));
// _revinclude's are counted but not _include's
assertEquals(3, b.getTotal().intValue());
ourLog.info(ids.toString());
}
/**
@ -603,26 +500,50 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testCountParam() throws Exception {
// NB this does not get used- The paging provider has its own limits built in
ourDaoConfig.setHardSearchLimit(100);
public void testEverythingOperation() throws Exception {
String methodName = "testEverythingOperation";
List<IBaseResource> resources = new ArrayList<IBaseResource>();
for (int i = 0; i < 100; i++) {
Organization org = new Organization();
org.setName("rpdstu2_testCountParam_01");
resources.add(org);
Organization org1 = new Organization();
org1.setName(methodName + "1");
IdDt orgId1 = ourClient.create().resource(org1).execute().getId();
Patient p = new Patient();
p.addName().addFamily(methodName);
p.getManagingOrganization().setReference(orgId1);
IdDt patientId = ourClient.create().resource(p).execute().getId();
Organization org2 = new Organization();
org2.setName(methodName + "1");
IdDt orgId2 = ourClient.create().resource(org2).execute().getId();
Device dev = new Device();
dev.setModel(methodName);
dev.getOwner().setReference(orgId2);
IdDt devId = ourClient.create().resource(dev).execute().getId();
Observation obs = new Observation();
obs.getSubject().setReference(patientId);
obs.getDevice().setReference(devId);
IdDt obsId = ourClient.create().resource(obs).execute().getId();
Encounter enc = new Encounter();
enc.getPatient().setReference(patientId);
IdDt encId = ourClient.create().resource(enc).execute().getId();
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
ca.uhn.fhir.model.dstu2.resource.Bundle b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
Set<IdDt> ids = new HashSet<IdDt>();
for (Entry next : b.getEntry()) {
ids.add(next.getResource().getId());
}
ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute();
Bundle found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).limitTo(10).execute();
assertEquals(100, found.getTotalResults().getValue().intValue());
assertEquals(10, found.getEntries().size());
assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2));
found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).limitTo(999).execute();
assertEquals(100, found.getTotalResults().getValue().intValue());
assertEquals(50, found.getEntries().size());
// _revinclude's are counted but not _include's
assertEquals(3, b.getTotal().intValue());
ourLog.info(ids.toString());
}
/**
@ -644,117 +565,29 @@ public class ResourceProviderDstu2Test {
}
/**
* See issue #52
* Test for issue #60
*/
@Test
public void testDocumentManifestResources() throws Exception {
ourFhirCtx.getResourceDefinition(Practitioner.class);
ourFhirCtx.getResourceDefinition(ca.uhn.fhir.model.dstu.resource.DocumentManifest.class);
public void testReadAllInstancesOfType() throws Exception {
Patient pat;
IGenericClient client = ourClient;
pat = new Patient();
pat.addIdentifier().setSystem("urn:system").setValue("testReadAllInstancesOfType_01");
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
int initialSize = client.search().forResource(DocumentManifest.class).execute().size();
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentmanifest.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(DocumentManifest.class).execute().size();
assertEquals(1, newSize - initialSize);
pat = new Patient();
pat.addIdentifier().setSystem("urn:system").setValue("testReadAllInstancesOfType_02");
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
{
Bundle returned = ourClient.search().forResource(Patient.class).encodedXml().execute();
assertThat(returned.size(), greaterThan(1));
assertEquals(BundleTypeEnum.SEARCHSET, returned.getType().getValueAsEnum());
}
/**
* See issue #52
*/
@Test
public void testDocumentReferenceResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DocumentReference.class).execute().size();
String resBody = IOUtils.toString(ResourceProviderDstu2Test.class.getResource("/documentreference.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(DocumentReference.class).execute().size();
assertEquals(1, newSize - initialSize);
{
Bundle returned = ourClient.search().forResource(Patient.class).encodedJson().execute();
assertThat(returned.size(), greaterThan(1));
}
/**
* See issue #52
*/
@Test
public void testDiagnosticOrderResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size();
DiagnosticOrder res = new DiagnosticOrder();
res.addIdentifier().setSystem("urn:foo").setValue("123");
client.create().resource(res).execute();
int newSize = client.search().forResource(DiagnosticOrder.class).execute().size();
assertEquals(1, newSize - initialSize);
}
private void delete(String theResourceType, String theParamName, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new StringClientParam(theParamName).matches().value(theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
@Test
public void testDeepChaining() {
delete("Location", Location.SP_NAME, "testDeepChainingL1");
delete("Location", Location.SP_NAME, "testDeepChainingL2");
deleteToken("Encounter", Encounter.SP_IDENTIFIER, "urn:foo", "testDeepChainingE1");
Location l1 = new Location();
l1.getNameElement().setValue("testDeepChainingL1");
IdDt l1id = ourClient.create().resource(l1).execute().getId();
Location l2 = new Location();
l2.getNameElement().setValue("testDeepChainingL2");
l2.getPartOf().setReference(l1id.toVersionless().toUnqualified());
IdDt l2id = ourClient.create().resource(l2).execute().getId();
Encounter e1 = new Encounter();
e1.addIdentifier().setSystem("urn:foo").setValue("testDeepChainingE1");
e1.getStatusElement().setValueAsEnum(EncounterStateEnum.IN_PROGRESS);
e1.getClassElementElement().setValueAsEnum(EncounterClassEnum.HOME);
ca.uhn.fhir.model.dstu2.resource.Encounter.Location location = e1.addLocation();
location.getLocation().setReference(l2id.toUnqualifiedVersionless());
location.setPeriod(new PeriodDt().setStartWithSecondsPrecision(new Date()).setEndWithSecondsPrecision(new Date()));
IdDt e1id = ourClient.create().resource(e1).execute().getId();
//@formatter:off
Bundle res = ourClient.search()
.forResource(Encounter.class)
.where(Encounter.IDENTIFIER.exactly().systemAndCode("urn:foo", "testDeepChainingE1"))
.include(Encounter.INCLUDE_LOCATION)
.include(Location.INCLUDE_PARTOF)
.execute();
//@formatter:on
assertEquals(3, res.size());
assertEquals(1, res.getResources(Encounter.class).size());
assertEquals(e1id.toUnqualifiedVersionless(), res.getResources(Encounter.class).get(0).getId().toUnqualifiedVersionless());
}
@Test
@ -804,6 +637,20 @@ public class ResourceProviderDstu2Test {
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
}
@Test
public void testSearchBundleDoesntIncludeTextElement() throws Exception {
HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json");
CloseableHttpResponse response = ourHttpClient.execute(read);
try {
String text = IOUtils.toString(response.getEntity().getContent());
ourLog.info(text);
assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
assertThat(text, not(containsString("\"text\",\"type\"")));
} finally {
response.close();
}
}
@Test
public void testSearchByIdentifier() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier01");
@ -874,6 +721,118 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testSearchWithInclude() throws Exception {
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude01");
IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
Patient pat = new Patient();
pat.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testSearchWithInclude02");
pat.getManagingOrganization().setReference(orgId);
ourClient.create().resource(pat).prettyPrint().encodedXml().execute().getId();
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Patient.class)
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier("urn:system:rpdstu2","testSearchWithInclude02"))
.include(Patient.INCLUDE_ORGANIZATION)
.prettyPrint()
.execute();
//@formatter:on
assertEquals(2, found.size());
assertEquals(Patient.class, found.getEntries().get(0).getResource().getClass());
assertEquals(BundleEntrySearchModeEnum.MATCH, found.getEntries().get(0).getSearchMode().getValueAsEnum());
assertEquals(BundleEntrySearchModeEnum.MATCH, found.getEntries().get(0).getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE));
assertThat(found.getEntries().get(0).getResource().getText().getDiv().getValueAsString(), containsString("<table class=\"hapiPropertyTable"));
assertEquals(Organization.class, found.getEntries().get(1).getResource().getClass());
assertEquals(BundleEntrySearchModeEnum.INCLUDE, found.getEntries().get(1).getSearchMode().getValueAsEnum());
assertEquals(BundleEntrySearchModeEnum.INCLUDE, found.getEntries().get(1).getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE));
}
@Test
public void testSearchWithMissing() throws Exception {
ourLog.info("Starting testSearchWithMissing");
String methodName = "testSearchWithMissing";
List<IResource> resources = new ArrayList<IResource>();
for (int i = 0; i < 20; i++) {
Organization org = new Organization();
org.setName(methodName + "_0" + i);
resources.add(org);
}
ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute();
Organization org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01");
org.setName(methodName + "name");
IdDt orgNotMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless();
org = new Organization();
org.addIdentifier().setSystem("urn:system:rpdstu2").setValue(methodName + "01");
IdDt orgMissing = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId().toUnqualifiedVersionless();
{
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Organization.class)
.where(Organization.NAME.isMissing(false))
.limitTo(100)
.prettyPrint()
.execute();
//@formatter:on
List<IdDt> list = toIdListUnqualifiedVersionless(found);
ourLog.info(methodName + ": " + list.toString());
assertThat("Wanted " + orgNotMissing + " but got: " + list, list, containsInRelativeOrder(orgNotMissing));
assertThat(list, not(containsInRelativeOrder(orgMissing)));
}
//@formatter:off
Bundle found = ourClient
.search()
.forResource(Organization.class)
.where(Organization.NAME.isMissing(true))
.limitTo(100)
.prettyPrint()
.execute();
//@formatter:on
List<IdDt> list = toIdListUnqualifiedVersionless(found);
ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing);
assertThat(list, not(containsInRelativeOrder(orgNotMissing)));
assertThat("Wanted " + orgMissing + " but found: " + list, list, containsInRelativeOrder(orgMissing));
}
/**
* Test for issue #60
*/
@Test
public void testStoreUtf8Characters() throws Exception {
Organization org = new Organization();
org.setName("測試醫院");
org.addIdentifier().setSystem("urn:system").setValue("testStoreUtf8Characters_01");
IdDt orgId = ourClient.create().resource(org).prettyPrint().encodedXml().execute().getId();
// Read back directly from the DAO
{
Organization returned = ourOrganizationDao.read(orgId);
String val = ourFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned);
ourLog.info(val);
assertThat(val, containsString("<name value=\"測試醫院\"/>"));
}
// Read back through the HTTP API
{
Organization returned = ourClient.read(Organization.class, orgId);
String val = ourFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned);
ourLog.info(val);
assertThat(val, containsString("<name value=\"測試醫院\"/>"));
}
}
@Test
public void testTryToCreateResourceWithReferenceThatDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testTryToCreateResourceWithReferenceThatDoesntExist01");
@ -919,6 +878,41 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testUpdateResourceConditional() throws IOException {
String methodName = "testUpdateResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
String resource = ourFhirCtx.newXmlParser().encodeResourceToString(pt);
HttpPost post = new HttpPost(ourServerBase + "/Patient?name=" + methodName);
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
IdDt id;
try {
assertEquals(201, response.getStatusLine().getStatusCode());
String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue();
assertThat(newIdString, startsWith(ourServerBase + "/Patient/"));
id = new IdDt(newIdString);
} finally {
response.close();
}
HttpPut put = new HttpPut(ourServerBase + "/Patient?name=" + methodName);
put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
response = ourHttpClient.execute(put);
try {
assertEquals(200, response.getStatusLine().getStatusCode());
IdDt newId = new IdDt(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue());
assertEquals(id.toVersionless(), newId.toVersionless()); // version shouldn't match for conditional update
assertNotEquals(id, newId);
} finally {
response.close();
}
}
@Test
public void testUpdateWithClientSuppliedIdWhichDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2");
@ -937,6 +931,68 @@ public class ResourceProviderDstu2Test {
}
@Test
public void testValidateResource() throws IOException {
Patient patient = new Patient();
patient.addName().addGiven("James");
patient.setBirthDate(new DateDt("2011-02-02"));
Parameters input = new Parameters();
input.addParameter().setName("resource").setResource(patient);
String inputStr = ourFhirCtx.newXmlParser().encodeResourceToString(input);
ourLog.info(inputStr);
HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate");
post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}
// @Test
public void testValidateResourceHuge() throws IOException {
Patient patient = new Patient();
patient.addName().addGiven("James" + StringUtils.leftPad("James", 1000000, 'A'));;
patient.setBirthDate(new DateDt("2011-02-02"));
Parameters input = new Parameters();
input.addParameter().setName("resource").setResource(patient);
String inputStr = ourFhirCtx.newXmlParser().encodeResourceToString(input);
ourLog.info(inputStr);
HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate");
post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}
private List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
List<IdDt> list = new ArrayList<IdDt>();
for (BundleEntry next : found.getEntries()) {
list.add(next.getResource().getId().toUnqualifiedVersionless());
}
return list;
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();

View File

@ -135,7 +135,7 @@ public class TestRestfulServer extends RestfulServer {
registerInterceptor(new ResponseHighlighterInterceptor());
/*
* Default to XML and pretty printing
* Default to JSON with pretty printing
*/
setDefaultPrettyPrint(true);
setDefaultResponseEncoding(EncodingEnum.JSON);

View File

@ -0,0 +1,8 @@
package ca.uhn.fhirtest.rp;
import ca.uhn.fhir.jpa.provider.JpaResourceProviderDstu2;
import ca.uhn.fhir.model.api.IResource;
public class FhirtestBaseResourceProviderDstu2<T extends IResource> extends JpaResourceProviderDstu2<T> {
}

View File

@ -32,7 +32,7 @@
<bean id="myLoggingInterceptor" class="ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor">
<property name="loggerName" value="fhirtest.access"/>
<property name="messageFormat"
value="Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}]"/>
value="Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"/>
</bean>
</beans>

View File

@ -36,6 +36,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.dstu.composite.AddressDt;
import ca.uhn.fhir.model.dstu.composite.AttachmentDt;
import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
@ -116,6 +117,30 @@ public class XmlParserTest {
assertEquals("OrgName", ((MyOrganization) parse.getSomeOrganization().getResource()).getName().getValue());
}
// @Test
public void testParseAndEncodeHugeValue() {
int len = 1000000;
byte[] bytes = new byte[len];
for (int i = 0; i < len; i++) {
bytes[i] = (byte) (Math.random() * Byte.MAX_VALUE);
}
AttachmentDt att = new AttachmentDt();
att.setData(bytes);
Observation obs = new Observation();
obs.setValue(att);
String str = ourCtx.newXmlParser().encodeResourceToString(obs);
assertThat(str.length(), Matchers.greaterThan(len));
obs = ourCtx.newXmlParser().parseResource(Observation.class, str);
att = (AttachmentDt) obs.getValue();
assertEquals(bytes, att.getData().getValue());
}
/**
* Test for #82 - Not yet enabled because the test won't pass
*/

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashSet;
@ -16,7 +17,7 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchParameter;
@ -48,7 +49,7 @@ public class ResourceMethodTest {
inputParams.add("firstName");
inputParams.add("lastName");
assertEquals(false, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False
assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False
}
@Test
@ -63,7 +64,7 @@ public class ResourceMethodTest {
Set<String> inputParams = new HashSet<String>();
inputParams.add("mrn");
assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True
assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True
}
@Test
@ -80,7 +81,7 @@ public class ResourceMethodTest {
inputParams.add("firstName");
inputParams.add("mrn");
assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True
assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True
}
@Test
@ -98,7 +99,7 @@ public class ResourceMethodTest {
inputParams.add("lastName");
inputParams.add("mrn");
Request params = Request.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams);
RequestDetails params = RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams);
boolean actual = rm.incomingServerRequestMatchesMethod(params);
assertTrue( actual); // True
}
@ -119,6 +120,6 @@ public class ResourceMethodTest {
inputParams.add("mrn");
inputParams.add("foo");
assertEquals(false, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False
assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False
}
}

View File

@ -35,11 +35,11 @@ import ca.uhn.fhir.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class ValidateTest {
public class ValidateDstu1Test {
private static CloseableHttpClient ourClient;
private static EncodingEnum ourLastEncoding;
private static String ourLastResourceBody;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateTest.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateDstu1Test.class);
private static int ourPort;
private static Server ourServer;

View File

@ -141,3 +141,4 @@ local.properties
/target/
/target/
/target/
/target/

View File

@ -0,0 +1,225 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
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.model.api.IResource;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class ValidateDstu2Test {
private static CloseableHttpClient ourClient;
private static EncodingEnum ourLastEncoding;
private static String ourLastResourceBody;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateDstu2Test.class);
private static int ourPort;
private static Server ourServer;
private static BaseOperationOutcome ourOutcomeToReturn;
private static ValidationModeEnum ourLastMode;
private static String ourLastProfile;
@Before()
public void before() {
ourLastResourceBody = null;
ourLastEncoding = null;
ourOutcomeToReturn = null;
ourLastMode = null;
ourLastProfile = null;
}
@Test
public void testValidateWithOptions() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
params.addParameter().setName("profile").setValue(new StringDt("http://foo"));
params.addParameter().setName("mode").setValue(new StringDt(ValidationModeEnum.CREATE.name()));
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome"));
assertEquals("http://foo", ourLastProfile);
assertEquals(ValidationModeEnum.CREATE, ourLastMode);
}
@Test
public void testValidate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome"));
}
@Test
public void testValidateWithResults() throws Exception {
ourOutcomeToReturn = new OperationOutcome();
ourOutcomeToReturn.addIssue().setDetails("FOOBAR");
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$validate");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String resp = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(resp, stringContainsInOrder("<OperationOutcome", "FOOBAR"));
}
@Test
public void testValidateWithNoParsed() throws Exception {
Organization org = new Organization();
org.addIdentifier().setValue("001");
org.addIdentifier().setValue("002");
Parameters params = new Parameters();
params.addParameter().setName("resource").setResource(org);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Organization/$validate");
httpPost.setEntity(new StringEntity(new FhirContext().newJsonParser().encodeResourceToString(params), ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(ourLastResourceBody, stringContainsInOrder("\"resourceType\":\"Organization\"", "\"identifier\"", "\"value\":\"001"));
assertEquals(EncodingEnum.JSON, ourLastEncoding);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider, new OrganizationProvider());
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();
}
public static class PatientProvider implements IResourceProvider {
@Validate()
public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue());
if (thePatient.getId().isEmpty() == false) {
id = thePatient.getId();
}
ourLastMode = theMode;
ourLastProfile = theProfile;
MethodOutcome outcome = new MethodOutcome(id.withVersion("002"));
outcome.setOperationOutcome(ourOutcomeToReturn);
return outcome;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
public static class OrganizationProvider implements IResourceProvider {
@Validate()
public MethodOutcome validate(@ResourceParam String theResourceBody, @ResourceParam EncodingEnum theEncoding) {
ourLastResourceBody = theResourceBody;
ourLastEncoding = theEncoding;
return new MethodOutcome(new IdDt("001"));
}
@Override
public Class<? extends IResource> getResourceType() {
return Organization.class;
}
}
}

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.Collections;
import java.util.HashMap;
@ -26,13 +28,18 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.slf4j.Logger;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
@ -51,8 +58,6 @@ public class LoggingInterceptorTest {
private static RestfulServer servlet;
private IServerInterceptor myInterceptor;
@Test
public void testRead() throws Exception {
@ -108,7 +113,62 @@ public class LoggingInterceptorTest {
assertThat(captor.getValue(), StringContains.containsString("metadata - "));
}
@Test
public void testOperationOnInstance() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setMessageFormat("${operationType} - ${operationName} - ${idOrResourceName}");
servlet.setInterceptors(Collections.singletonList((IServerInterceptor) interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$everything");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertEquals("extended-operation-instance - $everything - Patient/123", captor.getValue());
}
@Test
public void testOperationOnServer() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setMessageFormat("${operationType} - ${operationName} - ${idOrResourceName}");
servlet.setInterceptors(Collections.singletonList((IServerInterceptor) interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$everything");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertEquals("extended-operation-server - $everything - ", captor.getValue());
}
@Test
public void testOperationOnType() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setMessageFormat("${operationType} - ${operationName} - ${idOrResourceName}");
servlet.setInterceptors(Collections.singletonList((IServerInterceptor) interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$everything");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertEquals("extended-operation-type - $everything - Patient", captor.getValue());
}
@AfterClass
public static void afterClass() throws Exception {
@ -121,17 +181,17 @@ public class LoggingInterceptorTest {
servlet.setInterceptors(Collections.singletonList(myInterceptor));
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
servlet.setResourceProviders(new DummyPatientResourceProvider());
servlet.setPlainProviders(new PlainProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
@ -164,7 +224,7 @@ public class LoggingInterceptorTest {
patient.getName().add(new HumanNameDt());
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
patient.setGender(AdministrativeGenderEnum.FEMALE);
patient.getId().setValue("2");
idToPatient.put("2", patient);
}
@ -185,7 +245,6 @@ public class LoggingInterceptorTest {
return retVal;
}
/**
* Retrieve the resource by its identifier
*
@ -203,12 +262,27 @@ public class LoggingInterceptorTest {
}
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
@Operation(name = "$everything", idempotent = true)
public Bundle patientTypeOperation(@IdParam IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
Bundle retVal = new Bundle();
// Populate bundle with matching resources
return retVal;
}
@Operation(name = "$everything", idempotent = true)
public Bundle patientTypeOperation(@OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
Bundle retVal = new Bundle();
// Populate bundle with matching resources
return retVal;
}
private Patient createPatient1() {
Patient patient = new Patient();
patient.addIdentifier();
@ -218,11 +292,23 @@ public class LoggingInterceptorTest {
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
patient.setGender(AdministrativeGenderEnum.MALE);
patient.getId().setValue("1");
return patient;
}
}
public static class PlainProvider {
@Operation(name = "$everything", idempotent = true)
public Bundle patientTypeOperation(@OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
Bundle retVal = new Bundle();
// Populate bundle with matching resources
return retVal;
}
}
}

View File

@ -191,17 +191,36 @@
</dependencies>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<show>public</show>
</configuration>
</plugin>
</plugins>
</reporting>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${maven_license_plugin_version}</version>
<configuration>
<!-- Don't add UHN licenses to RI structures -->
<skipUpdateLicense>true</skipUpdateLicense>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>

File diff suppressed because one or more lines are too long

View File

@ -230,8 +230,8 @@
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerId>javac-with-errorprone</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<!-- <compilerId>javac-with-errorprone</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse> -->
</configuration>
<dependencies>
<dependency>

View File

@ -55,6 +55,9 @@
<action type="add">
Web tester UI now supports _revinclude
</action>
<action type="add">
LoggingInterceptor for server now supports logging DSTU2 extended operations by name
</action>
</release>
<release version="1.0" date="2015-May-8">
<action type="add">

View File

@ -1059,6 +1059,17 @@
operation tests whether a resource passes business validation, and would be
acceptable for saving to a server (e.g. by a create or update method).
</p>
<p class="doc_info_bubble">
<b>Note on FHIR versions:</b>
In FHIR DSTU1 the validate operation used a URL resembling <code>http://example.com/Patient/_validate</code>
with a resource in the HTTP POST body. In FHIR DSTU2, validate has been changed to use the
<a href="#extended_operations">extended operation</a> mechanism. It now uses a URL
resembling <code>http://example.com/Patient/$validate</code> and takes a
Parameters resource as input in the method body.<br/><br/>
The mechanism described below may be used for both DSTU1 and DSTU2+ servers, and HAPI
will automatically use the correct form depending on what FHIR version the
server is configured to use.
</p>
<p>
Validate methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Validate.html">@Validate</a>
@ -1107,11 +1118,20 @@
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
In the example above, only the <code>@ResourceParam</code> parameter is technically required, but
in DSTU2 you are encouraged to also add the following parameters:
</p>
<ul>
<li><b>@Validate.Mode ValidationModeEnum mode</b> - This is the validation mode (see the FHIR specification for information on this)</li>
<li><b>@Validate.Profile String profile</b> - This is the profile to validate against (see the FHIR specification for more information on this)</li>
</ul>
<p>
Example URL to invoke this method (this would be invoked using an HTTP POST,
with the resource in the POST body):
with a Parameters resource in the POST body):
<br />
<code>http://fhir.example.com/Patient/_validate</code>
<code>http://fhir.example.com/Patient/$validate</code>
</p>
<a name="system_conformance" />
@ -1508,6 +1528,7 @@
<code>http://fhir.example.com/Patient/123/Condition</code>
</p>
<a name="extended_operations"/>
</section>
<section name="Extended Operations">