Merge remote-tracking branch 'upstream/master'

This commit is contained in:
bdenton 2018-11-08 16:14:58 -08:00
commit 71dfed282c
30 changed files with 701 additions and 306 deletions

View File

@ -34,6 +34,7 @@ import org.thymeleaf.cache.ICacheEntryValidity;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
@ -65,6 +66,8 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private HashMap<String, String> myNameToNarrativeTemplate;
private TemplateEngine myProfileTemplateEngine;
private IMessageResolver resolver;
/**
* Constructor
*/
@ -166,11 +169,21 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
};
myProfileTemplateEngine.setDialect(dialect);
if (this.resolver != null) {
myProfileTemplateEngine.setMessageResolver(this.resolver);
}
}
myInitialized = true;
}
public void setMessageResolver(IMessageResolver resolver) {
this.resolver = resolver;
if (myProfileTemplateEngine != null && resolver != null) {
myProfileTemplateEngine.setMessageResolver(resolver);
}
}
/**
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative
* before it is returned.

View File

@ -0,0 +1,14 @@
package ca.uhn.fhir.rest.api;
public enum RequestFormatParamStyleEnum {
/**
* Do not include a _format parameter on requests
*/
NONE,
/**
* "xml" or "json"
*/
SHORT
}

View File

@ -1,5 +1,11 @@
package ca.uhn.fhir.rest.client.api;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
/*
@ -11,9 +17,9 @@ import java.util.List;
* 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.
@ -22,23 +28,15 @@ import java.util.List;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
public interface IRestfulClient {
/**
* Retrieve the contents at the given URL and parse them as a resource. This
* method could be used as a low level implementation of a read/vread/search
* operation.
*
* @param theResourceType
* The resource type to parse
* @param theUrl
* The URL to load
*
* @param theResourceType The resource type to parse
* @param theUrl The URL to load
* @return The parsed resource
*/
<T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl);
@ -49,6 +47,17 @@ public interface IRestfulClient {
*/
EncodingEnum getEncoding();
/**
* Specifies that the client should use the given encoding to do its
* queries. This means that the client will append the "_format" param
* to GET methods (read/search/etc), and will add an appropriate header for
* write methods.
*
* @param theEncoding The encoding to use in the request, or <code>null</code> not specify
* an encoding (which generally implies the use of XML). The default is <code>null</code>.
*/
void setEncoding(EncodingEnum theEncoding);
/**
* Returns the FHIR context associated with this client
*/
@ -76,25 +85,12 @@ public interface IRestfulClient {
*/
void registerInterceptor(IClientInterceptor theInterceptor);
/**
* Specifies that the client should use the given encoding to do its
* queries. This means that the client will append the "_format" param
* to GET methods (read/search/etc), and will add an appropriate header for
* write methods.
*
* @param theEncoding
* The encoding to use in the request, or <code>null</code> not specify
* an encoding (which generally implies the use of XML). The default is <code>null</code>.
*/
void setEncoding(EncodingEnum theEncoding);
/**
* Specifies that the client should request that the server respond with "pretty printing"
* enabled. Note that this is a non-standard parameter, not all servers will
* support it.
*
* @param thePrettyPrint
* The pretty print flag to use in the request (default is <code>false</code>)
*
* @param thePrettyPrint The pretty print flag to use in the request (default is <code>false</code>)
*/
void setPrettyPrint(Boolean thePrettyPrint);
@ -109,4 +105,8 @@ public interface IRestfulClient {
*/
void unregisterInterceptor(IClientInterceptor theInterceptor);
/**
* Configures what style of _format parameter should be used in requests
*/
void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle);
}

View File

@ -34,7 +34,7 @@ public interface IOperationUntyped {
* @param theParameters The parameters to use as input. May also be <code>null</code> if the operation
* does not require any input parameters.
*/
<T extends IBaseParameters> IOperationUntypedWithInput<T> withParameters(T theParameters);
<T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withParameters(T theParameters);
/**
* The operation does not require any input parameters

View File

@ -38,6 +38,8 @@ public interface IOperationUntypedWithInputAndPartialOutput<T extends IBaseParam
IOperationUntypedWithInputAndPartialOutput<T> andParameter(String theName, IBase theValue);
/**
* Adds a URL parameter to the request.
*
* Use chained method calls to construct a Parameters input. This form is a convenience
* in order to allow simple method chaining to be used to build up a parameters
* resource for the input of an operation without needing to manually construct one.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.client.impl;
* 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.
@ -20,49 +20,11 @@ package ca.uhn.fhir.rest.client.impl;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.util.XmlDetectionUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.client.api.*;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
@ -72,7 +34,19 @@ import ca.uhn.fhir.rest.client.method.IClientResponseHandlerHandlesBinary;
import ca.uhn.fhir.rest.client.method.MethodUtil;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.XmlUtil;
import ca.uhn.fhir.util.XmlDetectionUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseClient implements IRestfulClient {
@ -86,16 +60,17 @@ public abstract class BaseClient implements IRestfulClient {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
private final IHttpClient myClient;
private final RestfulClientFactory myFactory;
private final String myUrlBase;
private boolean myDontValidateConformance;
private EncodingEnum myEncoding = null; // default unspecified (will be XML)
private final RestfulClientFactory myFactory;
private List<IClientInterceptor> myInterceptors = new ArrayList<IClientInterceptor>();
private boolean myKeepResponses = false;
private IHttpResponse myLastResponse;
private String myLastResponseBody;
private Boolean myPrettyPrint = false;
private SummaryEnum mySummary;
private final String myUrlBase;
private RequestFormatParamStyleEnum myRequestFormatParamStyle = RequestFormatParamStyleEnum.SHORT;
BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
super();
@ -121,10 +96,12 @@ public abstract class BaseClient implements IRestfulClient {
protected Map<String, List<String>> createExtraParams() {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
}
if (isPrettyPrint()) {
@ -150,6 +127,17 @@ public abstract class BaseClient implements IRestfulClient {
return myEncoding;
}
/**
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
*/
@Override
public void setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
// return this;
}
/**
* {@inheritDoc}
*/
@ -192,10 +180,21 @@ public abstract class BaseClient implements IRestfulClient {
return mySummary;
}
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
public String getUrlBase() {
return myUrlBase;
}
@Override
public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) {
Validate.notNull(theRequestFormatParamStyle, "theRequestFormatParamStyle must not be null");
myRequestFormatParamStyle = theRequestFormatParamStyle;
}
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) {
return invokeClient(theContext, binding, clientInvocation, false);
}
@ -219,10 +218,12 @@ public abstract class BaseClient implements IRestfulClient {
Map<String, List<String>> params = createExtraParams();
if (clientInvocation instanceof HttpGetClientInvocation) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
}
}
@ -252,7 +253,7 @@ public abstract class BaseClient implements IRestfulClient {
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
if (theCacheControlDirective.getMaxResults() != null) {
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS+"="+ Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS + "=" + Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
}
if (b.length() > 0) {
httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
@ -397,6 +398,13 @@ public abstract class BaseClient implements IRestfulClient {
return myKeepResponses;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* Returns the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
@ -406,6 +414,17 @@ public abstract class BaseClient implements IRestfulClient {
return Boolean.TRUE.equals(myPrettyPrint);
}
/**
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
// return this;
}
private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) {
if (myKeepResponses) {
myLastResponse = response;
@ -438,55 +457,12 @@ public abstract class BaseClient implements IRestfulClient {
myDontValidateConformance = theDontValidateConformance;
}
/**
* Sets the encoding that will be used on requests. Default is <code>null</code>, which means the client will not
* explicitly request an encoding. (This is perfectly acceptable behaviour according to the FHIR specification. In
* this case, the server will choose which encoding to return, and the client can handle either XML or JSON)
*/
@Override
public void setEncoding(EncodingEnum theEncoding) {
myEncoding = theEncoding;
// return this;
}
/**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/
public void setKeepResponses(boolean theKeepResponses) {
myKeepResponses = theKeepResponses;
}
/**
* Sets the pretty print flag, which is a request to the server for it to return "pretty printed" responses. Note
* that this is currently a non-standard flag (_pretty) which is supported only by HAPI based servers (and any other
* servers which might implement it).
*/
@Override
public void setPrettyPrint(Boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
// return this;
}
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
@Override
public void unregisterInterceptor(IClientInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
}
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
preferResponseTypes.add(thePreferResponseType);
}
return preferResponseTypes;
}
protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse;
@ -568,4 +544,13 @@ public abstract class BaseClient implements IRestfulClient {
}
}
static ArrayList<Class<? extends IBaseResource>> toTypeList(Class<? extends IBaseResource> thePreferResponseType) {
ArrayList<Class<? extends IBaseResource>> preferResponseTypes = null;
if (thePreferResponseType != null) {
preferResponseTypes = new ArrayList<Class<? extends IBaseResource>>(1);
preferResponseTypes.add(thePreferResponseType);
}
return preferResponseTypes;
}
}

View File

@ -1276,7 +1276,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseParameters> IOperationUntypedWithInput<T> withNoParameters(Class<T> theOutputParameterType) {
public <T extends IBaseParameters> IOperationUntypedWithInputAndPartialOutput<T> withNoParameters(Class<T> theOutputParameterType) {
Validate.notNull(theOutputParameterType, "theOutputParameterType may not be null");
RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType);
if (def == null) {
@ -1307,9 +1307,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings({"unchecked"})
@Override
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) {
public IOperationUntypedWithInputAndPartialOutput withParameters(IBaseParameters theParameters) {
Validate.notNull(theParameters, "theParameters can not be null");
myParameters = theParameters;
myParametersDef = myContext.getResourceDefinition(theParameters.getClass());
return this;
}
@ -1445,7 +1446,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer);
Map<String, List<String>> params = new HashMap<String, List<String>>();
Map<String, List<String>> params = new HashMap<>();
return invoke(params, binding, invocation);
}

View File

@ -22,6 +22,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
import java.io.*;
import java.util.List;
import java.util.Map.Entry;
import javax.ws.rs.core.MediaType;
@ -104,8 +105,11 @@ public class JaxRsResponse extends RestfulResponse<JaxRsRequest> {
private ResponseBuilder buildResponse(int statusCode) {
ResponseBuilder response = Response.status(statusCode);
for (Entry<String, String> header : getHeaders().entrySet()) {
response.header(header.getKey(), header.getValue());
for (Entry<String, List<String>> header : getHeaders().entrySet()) {
final String key = header.getKey();
for (String value : header.getValue()) {
response.header(key, value);
}
}
return response;
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jaxrs.server.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
@ -10,6 +11,7 @@ import java.util.Set;
import javax.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.junit.Before;
import org.junit.Test;
@ -108,10 +110,24 @@ public class JaxRsResponseTest {
assertEquals("application/xml+fhir; charset=UTF-8", result.getHeaderString(Constants.HEADER_CONTENT_TYPE));
}
@Test
public void addMultipleHeaderValues() throws IOException {
response.addHeader("Authorization", "Basic");
response.addHeader("Authorization", "Bearer");
response.addHeader("Cache-Control", "no-cache, no-store");
final IBaseBinary binary = new Binary();
binary.setContentType("abc");
binary.setContent(new byte[] { 1 });
final Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theSummaryMode, 200, false, false, this.request);
assertThat(result.getHeaders().get("Authorization"), Matchers.contains("Basic", "Bearer"));
assertThat(result.getHeaders().get("Cache-Control"), Matchers.contains("no-cache, no-store"));
}
private Patient createPatient() {
Patient theResource = new Patient();
theResource.setId(new IdDt(15L));
return theResource;
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2018 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 org.hibernate.cfg.AvailableSettings;
import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;

View File

@ -23,9 +23,9 @@ import java.util.*;
* 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.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* 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.
@ -43,10 +43,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.*;
import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair;
@ -86,17 +83,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
@Autowired
private DaoRegistry myDaoRegistry;
public static boolean isPlaceholder(IIdType theId) {
if (theId != null && theId.getValue() != null) {
return theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:");
}
return false;
}
private static String toStatusString(int theStatusCode) {
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}
private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
}
@ -164,7 +150,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
return defaultString(theId.getValue()).startsWith(URN_PREFIX);
}
public void setDao(BaseHapiFhirDao theDao) {
myDao = theDao;
}
@ -188,6 +173,40 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
}
public BUNDLE collection(final RequestDetails theRequestDetails, BUNDLE theRequest) {
String transactionType = myVersionAdapter.getBundleType(theRequest);
if (!org.hl7.fhir.r4.model.Bundle.BundleType.COLLECTION.toCode().equals(transactionType)) {
throw new InvalidRequestException("Can not process collection Bundle of type: " + transactionType);
}
ourLog.info("Beginning storing collection with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
BUNDLE resp = myVersionAdapter.createBundle(org.hl7.fhir.r4.model.Bundle.BundleType.BATCHRESPONSE.toCode());
List<IBaseResource> resources = new ArrayList<>();
for (final BUNDLEENTRY nextRequestEntry : myVersionAdapter.getEntries(theRequest)) {
IBaseResource resource = myVersionAdapter.getResource(nextRequestEntry);
resources.add(resource);
}
BUNDLE transactionBundle = myVersionAdapter.createBundle("transaction");
for (IBaseResource next : resources) {
BUNDLEENTRY entry = myVersionAdapter.addEntry(transactionBundle);
myVersionAdapter.setResource(entry, next);
myVersionAdapter.setRequestVerb(entry, "PUT");
myVersionAdapter.setRequestUrl(entry, next.getIdElement().toUnqualifiedVersionless().getValue());
}
transaction(theRequestDetails, transactionBundle);
return resp;
}
private BUNDLE batch(final RequestDetails theRequestDetails, BUNDLE theRequest) {
ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis();
@ -255,6 +274,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
validateDependencies();
String transactionType = myVersionAdapter.getBundleType(theRequest);
if (org.hl7.fhir.r4.model.Bundle.BundleType.BATCH.toCode().equals(transactionType)) {
return batch(theRequestDetails, theRequest);
}
@ -846,18 +866,10 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
String getEntryRequestIfNoneMatch(BUNDLEENTRY theEntry);
void setResponseOutcome(BUNDLEENTRY theEntry, IBaseOperationOutcome theOperationOutcome);
}
private static class BaseServerResponseExceptionHolder {
private BaseServerResponseException myException;
void setRequestVerb(BUNDLEENTRY theEntry, String theVerb);
public BaseServerResponseException getException() {
return myException;
}
public void setException(BaseServerResponseException myException) {
this.myException = myException;
}
void setRequestUrl(BUNDLEENTRY theEntry, String theUrl);
}
/**
@ -963,4 +975,27 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
}
private static class BaseServerResponseExceptionHolder {
private BaseServerResponseException myException;
public BaseServerResponseException getException() {
return myException;
}
public void setException(BaseServerResponseException myException) {
this.myException = myException;
}
}
public static boolean isPlaceholder(IIdType theId) {
if (theId != null && theId.getValue() != null) {
return theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:");
}
return false;
}
private static String toStatusString(int theStatusCode) {
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}
}

View File

@ -21,9 +21,9 @@ import java.util.Map;
* 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.

View File

@ -150,4 +150,14 @@ public class TransactionProcessorVersionAdapterDstu3 implements TransactionProce
theEntry.getResponse().setOutcome((Resource) theOperationOutcome);
}
@Override
public void setRequestVerb(Bundle.BundleEntryComponent theEntry, String theVerb) {
theEntry.getRequest().setMethod(Bundle.HTTPVerb.fromCode(theVerb));
}
@Override
public void setRequestUrl(Bundle.BundleEntryComponent theEntry, String theUrl) {
theEntry.getRequest().setUrl(theUrl);
}
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.r4;
* 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.
@ -23,12 +23,12 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Resource;
import java.util.Date;
import java.util.List;
@ -150,4 +150,14 @@ public class TransactionProcessorVersionAdapterR4 implements TransactionProcesso
theEntry.getResponse().setOutcome((Resource) theOperationOutcome);
}
@Override
public void setRequestVerb(Bundle.BundleEntryComponent theEntry, String theVerb) {
theEntry.getRequest().setMethod(Bundle.HTTPVerb.fromCode(theVerb));
}
@Override
public void setRequestUrl(Bundle.BundleEntryComponent theEntry, String theUrl) {
theEntry.getRequest().setUrl(theUrl);
}
}

View File

@ -2,12 +2,14 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.GZipUtil;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -322,6 +324,20 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
}
@Test
@Ignore
public void testProcessCollectionAsBatch() throws IOException {
byte[] inputBytes = IOUtils.toByteArray(getClass().getResourceAsStream("/dstu3/Reilly_Libby_73.json.gz"));
String input = GZipUtil.decompress(inputBytes);
Bundle bundle = myFhirCtx.newJsonParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(Bundle.class, input);
ourLog.info("Bundle has {} resources", bundle);
Bundle output = mySystemDao.transaction(mySrd, bundle);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
}
/**
* See #410
*/
@ -3040,7 +3056,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
@Test
public void testTransactionWithReplacement() {
byte[] bytes = new byte[] {0, 1, 2, 3, 4};
byte[] bytes = new byte[]{0, 1, 2, 3, 4};
Binary binary = new Binary();
binary.setId(IdType.newRandomUuid());

View File

@ -21,9 +21,7 @@ package ca.uhn.fhir.rest.server;
*/
import java.io.IOException;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import org.hl7.fhir.instance.model.api.*;
@ -35,7 +33,7 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
private IIdType myOperationResourceId;
private IPrimitiveType<Date> myOperationResourceLastUpdated;
private ConcurrentHashMap<String, String> theHeaders = new ConcurrentHashMap<String, String>();
private Map<String, List<String>> theHeaders = new HashMap<>();
private T theRequestDetails;
public RestfulResponse(T requestDetails) {
@ -44,14 +42,14 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
@Override
public void addHeader(String headerKey, String headerValue) {
this.getHeaders().put(headerKey, headerValue);
this.getHeaders().computeIfAbsent(headerKey, k -> new ArrayList<>()).add(headerValue);
}
/**
* Get the http headers
* @return the headers
*/
public ConcurrentHashMap<String, String> getHeaders() {
public Map<String, List<String>> getHeaders() {
return theHeaders;
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
* 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.

View File

@ -24,6 +24,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.List;
import java.util.Map.Entry;
import java.util.zip.GZIPOutputStream;
@ -75,8 +76,18 @@ public class ServletRestfulResponse extends RestfulResponse<ServletRequestDetail
private void addHeaders() {
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
getRequestDetails().getServer().addHeadersToResponse(theHttpResponse);
for (Entry<String, String> header : getHeaders().entrySet()) {
theHttpResponse.setHeader(header.getKey(), header.getValue());
for (Entry<String, List<String>> header : getHeaders().entrySet()) {
final String key = header.getKey();
boolean first = true;
for (String value : header.getValue()) {
// existing headers should be overridden
if (first) {
theHttpResponse.setHeader(key, value);
first = false;
} else {
theHttpResponse.addHeader(key, value);
}
}
}
}

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
/**
* Unit tests of {@link RestfulResponse}.
*/
public class RestfulResponseTest {
@Test
public void addMultipleHeaderValues() {
@SuppressWarnings("unchecked")
final RestfulResponse<?> restfulResponse =
mock(RestfulResponse.class, withSettings()
.useConstructor((RequestDetails) null).defaultAnswer(CALLS_REAL_METHODS));
restfulResponse.addHeader("Authorization", "Basic");
restfulResponse.addHeader("Authorization", "Bearer");
restfulResponse.addHeader("Cache-Control", "no-cache, no-store");
assertEquals(2, restfulResponse.getHeaders().size());
assertThat(restfulResponse.getHeaders().get("Authorization"), Matchers.contains("Basic", "Bearer"));
assertThat(restfulResponse.getHeaders().get("Cache-Control"), Matchers.contains("no-cache, no-store"));
}
}

View File

@ -0,0 +1,65 @@
package ca.uhn.fhir.rest.server.servlet;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* Unit tests of {@link ServletRestfulResponse}.
*/
public class ServletRestfulResponseTest {
@Mock
private RestfulServer server;
@Mock
private ServletOutputStream servletOutputStream;
@Mock
private HttpServletResponse servletResponse;
private ServletRequestDetails requestDetails;
private ServletRestfulResponse response;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Before
public void init() throws IOException {
Mockito.when(servletResponse.getOutputStream()).thenReturn(servletOutputStream);
requestDetails = new ServletRequestDetails();
requestDetails.setServer(server);
requestDetails.setServletResponse(servletResponse);
response = new ServletRestfulResponse(requestDetails);
}
@Test
public void addMultipleHeaderValues() throws IOException {
final ServletRestfulResponse response = new ServletRestfulResponse(requestDetails);
response.addHeader("Authorization", "Basic");
response.addHeader("Authorization", "Bearer");
response.addHeader("Cache-Control", "no-cache, no-store");
response.getResponseWriter(200, "Status", "text/plain", "UTF-8", false);
final InOrder orderVerifier = Mockito.inOrder(servletResponse);
orderVerifier.verify(servletResponse).setHeader(eq("Authorization"), eq("Basic"));
orderVerifier.verify(servletResponse).addHeader(eq("Authorization"), eq("Bearer"));
verify(servletResponse).setHeader(eq("Cache-Control"), eq("no-cache, no-store"));
}
}

View File

@ -2651,6 +2651,11 @@ public class GenericClientDstu2Test {
// nothing
}
@Override
public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) {
// nothing
}
@Override
public EncodingEnum getEncoding() {
// TODO Auto-generated method stub

View File

@ -5,7 +5,12 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
@ -28,6 +33,8 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.templateresource.ITemplateResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
@ -77,6 +84,52 @@ public class DefaultThymeleafNarrativeGeneratorDstu3Test {
}
@Test
public void testTranslations() throws DataFormatException {
CustomThymeleafNarrativeGenerator customGen = new CustomThymeleafNarrativeGenerator("classpath:/testnarrative.properties");
customGen.setIgnoreFailures(false);
customGen.setIgnoreMissingTemplates(false);
FhirContext ctx = FhirContext.forDstu3();
ctx.setNarrativeGenerator(customGen);
Patient value = new Patient();
value.addIdentifier().setSystem("urn:names").setValue("123456");
value.addName().setFamily("blow").addGiven("joe").addGiven((String) null).addGiven("john");
//@formatter:off
value.addAddress()
.addLine("123 Fake Street").addLine("Unit 1")
.setCity("Toronto").setState("ON").setCountry("Canada");
//@formatter:on
value.setBirthDate(new Date());
Transformer transformer = new Transformer() {
@Override
public Object transform(Object input) {
return "UNTRANSLATED:" + input;
}};
Map translations = new HashMap<>();
translations.put("some_text", "Some beautiful proze");
customGen.setMessageResolver(new StandardMessageResolver() {
@Override
protected Map<String, String> resolveMessagesForTemplate(String template,
ITemplateResource templateResource, Locale locale) {
return LazyMap.decorate(translations, transformer);
}
});
Narrative narrative = new Narrative();
customGen.generateNarrative(ctx, value, narrative);
String output = narrative.getDiv().getValueAsString();
ourLog.info(output);
assertThat(output, StringContains.containsString("Some beautiful proze"));
assertThat(output, StringContains.containsString("UNTRANSLATED:other_text"));
}
@Test
public void testGenerateDiagnosticReport() throws DataFormatException {

View File

@ -0,0 +1,4 @@
<div>
<p th:text="#{some_text}">Some Text</p>
<p th:text="#{other_text}">Some Text</p>
</div>

View File

@ -0,0 +1,2 @@
patient.class=org.hl7.fhir.dstu3.model.Patient
patient.narrative=classpath:/TestPatient.html

View File

@ -1,17 +1,13 @@
package ca.uhn.fhir.rest.client;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RequestFormatParamStyleEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.util.RandomServerPortProvider;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.VersionUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -20,9 +16,7 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -40,8 +34,9 @@ public class ClientHeadersR4Test {
private static Server ourServer;
private static String ourServerBase;
private static HashMap<String, List<String>> ourHeaders;
private static IGenericClient ourClient;
private static HashMap<String, String[]> ourParams;
private static String ourMethod;
private IGenericClient myClient;
@Before
public void before() {
@ -49,34 +44,125 @@ public class ClientHeadersR4Test {
ourMethod = null;
}
private String expectedUserAgent() {
return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.R4.getFhirVersionString() + "/R4; apache)";
}
private byte[] extractBodyAsByteArray(ArgumentCaptor<HttpUriRequest> capt) throws IOException {
byte[] body = IOUtils.toByteArray(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent());
return body;
}
private String extractBodyAsString(ArgumentCaptor<HttpUriRequest> capt) throws IOException {
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(0)).getEntity().getContent(), "UTF-8");
return body;
}
@Test
public void testCreateWithPreferRepresentationServerReturnsResource() throws Exception {
public void testReadXml() {
myClient
.read()
.resource("Patient")
.withId(123L)
.encodedXml()
.execute();
assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", ourHeaders.get(Constants.HEADER_ACCEPT).get(0));
assertEquals("xml", ourParams.get(Constants.PARAM_FORMAT)[0]);
}
@Test
public void testReadXmlNoParam() {
myClient.setFormatParamStyle(RequestFormatParamStyleEnum.NONE);
myClient
.read()
.resource("Patient")
.withId(123L)
.encodedXml()
.execute();
assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", ourHeaders.get(Constants.HEADER_ACCEPT).get(0));
assertEquals(null, ourParams.get(Constants.PARAM_FORMAT));
}
@Test
public void testReadJson() {
myClient
.read()
.resource("Patient")
.withId(123L)
.encodedJson()
.execute();
assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", ourHeaders.get(Constants.HEADER_ACCEPT).get(0));
assertEquals("json", ourParams.get(Constants.PARAM_FORMAT)[0]);
}
@Test
public void testReadJsonNoParam() {
myClient.setFormatParamStyle(RequestFormatParamStyleEnum.NONE);
myClient
.read()
.resource("Patient")
.withId(123L)
.encodedJson()
.execute();
assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", ourHeaders.get(Constants.HEADER_ACCEPT).get(0));
assertEquals(null, ourParams.get(Constants.PARAM_FORMAT));
}
@Test
public void testReadXmlDisable() {
myClient
.read()
.resource("Patient")
.withId(123L)
.encodedXml()
.execute();
assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", ourHeaders.get(Constants.HEADER_ACCEPT).get(0));
assertEquals("xml", ourParams.get(Constants.PARAM_FORMAT)[0]);
}
@Test
public void testCreateWithPreferRepresentationServerReturnsResource() {
final Patient resp1 = new Patient();
resp1.setActive(true);
MethodOutcome resp = ourClient.create().resource(resp1).execute();
MethodOutcome resp = myClient.create().resource(resp1).execute();
assertNotNull(resp);
assertEquals(1, ourHeaders.get(Constants.HEADER_CONTENT_TYPE).size());
assertEquals("application/fhir+xml; charset=UTF-8", ourHeaders.get(Constants.HEADER_CONTENT_TYPE).get(0));
}
@Before
public void beforeCreateClient() {
myClient = ourCtx.newRestfulGenericClient(ourServerBase);
}
private static class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (ourHeaders != null) {
fail();
}
ourHeaders = new HashMap<>();
ourParams = new HashMap<>(req.getParameterMap());
ourMethod = req.getMethod();
Enumeration<String> names = req.getHeaderNames();
while (names.hasMoreElements()) {
String nextName = names.nextElement();
ourHeaders.put(nextName, new ArrayList<>());
Enumeration<String> values = req.getHeaders(nextName);
while (values.hasMoreElements()) {
ourHeaders.get(nextName).add(values.nextElement());
}
}
resp.setStatus(200);
if (req.getMethod().equals("GET")) {
resp.setContentType("application/json");
resp.getWriter().append("{\"resourceType\":\"Patient\"}");
resp.getWriter().close();
}
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
@ -94,7 +180,6 @@ public class ClientHeadersR4Test {
ourServerBase = "http://localhost:" + myPort + "/fhir/context";
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(new TestServlet());
@ -105,29 +190,4 @@ public class ClientHeadersR4Test {
}
private static class TestServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if (ourHeaders != null) {
fail();
}
ourHeaders = new HashMap<>();
ourMethod = req.getMethod();
Enumeration<String> names = req.getHeaderNames();
while (names.hasMoreElements()) {
String nextName = names.nextElement();
ourHeaders.put(nextName, new ArrayList<String>());
Enumeration<String> values = req.getHeaders(nextName);
while (values.hasMoreElements()) {
ourHeaders.get(nextName).add(values.nextElement());
}
}
resp.setStatus(200);
}
}
}

View File

@ -1,41 +1,45 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.junit.*;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.google.common.base.Charsets;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.*;
import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.util.TestUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class OperationClientR4Test {
@ -48,13 +52,6 @@ public class OperationClientR4Test {
private ArgumentCaptor<HttpUriRequest> capt;
private IGenericClient ourGenClient;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before
public void before() throws Exception {
ourCtx = FhirContext.forR4();
@ -64,7 +61,7 @@ public class OperationClientR4Test {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
Parameters outParams = new Parameters();
outParams.addParameter().setName("FOO");
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
@ -75,7 +72,7 @@ public class OperationClientR4Test {
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
public InputStream answer(InvocationOnMock theInvocation) {
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
}
});
@ -95,10 +92,10 @@ public class OperationClientR4Test {
.execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(value.getEntity().getContent());
ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
@ -110,7 +107,7 @@ public class OperationClientR4Test {
}
@Test
public void testNonRepeatingGenericUsingUrl() throws Exception {
public void testNonRepeatingGenericUsingUrl() {
ourGenClient
.operation()
.onServer()
@ -121,14 +118,31 @@ public class OperationClientR4Test {
.execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpGet value = (HttpGet) capt.getAllValues().get(0);
assertEquals("http://foo/$nonrepeating?valstr=str&valtok=sys2%7Cval2", value.getURI().toASCIIString());
}
@Test
public void testNonRepeatingGenericUsingUrl2() {
ourGenClient
.operation()
.onServer()
.named("nonrepeating")
.withParameters(new Parameters())
.andSearchParameter("valstr", new StringParam("str"))
.andSearchParameter("valtok", new TokenParam("sys2", "val2"))
.useHttpGet()
.execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpGet value = (HttpGet) capt.getAllValues().get(0);
assertEquals("http://foo/$nonrepeating?valstr=str&valtok=sys2%7Cval2", value.getURI().toASCIIString());
}
@Test
public void testOperationOnInstanceWithIncompleteInstanceId() throws Exception {
public void testOperationOnInstanceWithIncompleteInstanceId() {
try {
ourGenClient
.operation()
@ -146,10 +160,10 @@ public class OperationClientR4Test {
public void testNonRepeatingUsingParameters() throws Exception {
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(value.getEntity().getContent());
ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
@ -160,48 +174,52 @@ public class OperationClientR4Test {
assertEquals("sys|val", ((StringType) request.getParameter().get(1).getValue()).getValue());
}
public interface IOpClient extends IBasicClient {
public interface IOpClient extends IBasicClient {
@Operation(name = "$andlist", idempotent = true)
public Parameters andlist(
//@formatter:off
@OperationParam(name="valstr", max=10) StringAndListParam theValStr,
@OperationParam(name="valtok", max=10) TokenAndListParam theValTok
//@formatter:on
Parameters andlist(
//@formatter:off
@OperationParam(name = "valstr", max = 10) StringAndListParam theValStr,
@OperationParam(name = "valtok", max = 10) TokenAndListParam theValTok
//@formatter:on
);
@Operation(name = "$andlist-withnomax", idempotent = true)
public Parameters andlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") StringAndListParam theValStr,
@OperationParam(name="valtok") TokenAndListParam theValTok
//@formatter:on
Parameters andlistWithNoMax(
//@formatter:off
@OperationParam(name = "valstr") StringAndListParam theValStr,
@OperationParam(name = "valtok") TokenAndListParam theValTok
//@formatter:on
);
@Operation(name = "$nonrepeating", idempotent = true)
public Parameters nonrepeating(
//@formatter:off
@OperationParam(name="valstr") StringParam theValStr,
@OperationParam(name="valtok") TokenParam theValTok
//@formatter:on
Parameters nonrepeating(
//@formatter:off
@OperationParam(name = "valstr") StringParam theValStr,
@OperationParam(name = "valtok") TokenParam theValTok
//@formatter:on
);
@Operation(name = "$orlist", idempotent = true)
public Parameters orlist(
//@formatter:off
@OperationParam(name="valstr", max=10) List<StringOrListParam> theValStr,
@OperationParam(name="valtok", max=10) List<TokenOrListParam> theValTok
//@formatter:on
Parameters orlist(
//@formatter:off
@OperationParam(name = "valstr", max = 10) List<StringOrListParam> theValStr,
@OperationParam(name = "valtok", max = 10) List<TokenOrListParam> theValTok
//@formatter:on
);
@Operation(name = "$orlist-withnomax", idempotent = true)
public Parameters orlistWithNoMax(
//@formatter:off
@OperationParam(name="valstr") List<StringOrListParam> theValStr,
@OperationParam(name="valtok") List<TokenOrListParam> theValTok
//@formatter:on
Parameters orlistWithNoMax(
//@formatter:off
@OperationParam(name = "valstr") List<StringOrListParam> theValStr,
@OperationParam(name = "valtok") List<TokenOrListParam> theValTok
//@formatter:on
);
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -433,6 +433,7 @@
</developer>
<developer>
<id>RuthAlk</id>
<name>Ruth Alkema</name>
</developer>
<developer>
<id>Tastelezz</id>
@ -473,6 +474,10 @@
<developer>
<id>jbalbien</id>
</developer>
<developer>
<id>volsch</id>
<name>Volker Schmidt</name>
</developer>
</developers>
<licenses>

View File

@ -164,6 +164,19 @@
now possible to disallow this action, to only allow alphanumeric IDs (the default
and only option previously) or allow any IDs including alphanumeric.
</action>
<action type="add" issue="1103" dev="ruthakm">
It is now possible to use your own IMessageResolver instance in the narrative
generator. Thanks to Ruth Alkema for the pull request!
</action>
<action type="fix" issue="1071" dev="volsch">
When restful reponses tried to return multiple instances of the same response header,
some instances were discarded. Thanks to Volker Schmidt for the pull request!
</action>
<action type="add">
The REST client now allows for configurable behaviour as to whether a
<![CDATA[<code>_format</code>]]>
parameter should be included in requests.
</action>
</release>
<release version="3.5.0" date="2018-09-17">