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.Context;
import org.thymeleaf.context.ITemplateContext; import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName; import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.model.IProcessableElementTag; import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.IProcessor; import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor; import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
@ -65,6 +66,8 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private HashMap<String, String> myNameToNarrativeTemplate; private HashMap<String, String> myNameToNarrativeTemplate;
private TemplateEngine myProfileTemplateEngine; private TemplateEngine myProfileTemplateEngine;
private IMessageResolver resolver;
/** /**
* Constructor * Constructor
*/ */
@ -166,11 +169,21 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}; };
myProfileTemplateEngine.setDialect(dialect); myProfileTemplateEngine.setDialect(dialect);
if (this.resolver != null) {
myProfileTemplateEngine.setMessageResolver(this.resolver);
}
} }
myInitialized = true; 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 * If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative
* before it is returned. * 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; 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; import java.util.List;
/* /*
@ -11,9 +17,9 @@ import java.util.List;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -22,23 +28,15 @@ import java.util.List;
* #L% * #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 { public interface IRestfulClient {
/** /**
* Retrieve the contents at the given URL and parse them as a resource. This * 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 * method could be used as a low level implementation of a read/vread/search
* operation. * operation.
* *
* @param theResourceType * @param theResourceType The resource type to parse
* The resource type to parse * @param theUrl The URL to load
* @param theUrl
* The URL to load
* @return The parsed resource * @return The parsed resource
*/ */
<T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl); <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl);
@ -49,6 +47,17 @@ public interface IRestfulClient {
*/ */
EncodingEnum getEncoding(); 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 * Returns the FHIR context associated with this client
*/ */
@ -76,25 +85,12 @@ public interface IRestfulClient {
*/ */
void registerInterceptor(IClientInterceptor theInterceptor); 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" * 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 * enabled. Note that this is a non-standard parameter, not all servers will
* support it. * support it.
* *
* @param thePrettyPrint * @param thePrettyPrint The pretty print flag to use in the request (default is <code>false</code>)
* The pretty print flag to use in the request (default is <code>false</code>)
*/ */
void setPrettyPrint(Boolean thePrettyPrint); void setPrettyPrint(Boolean thePrettyPrint);
@ -109,4 +105,8 @@ public interface IRestfulClient {
*/ */
void unregisterInterceptor(IClientInterceptor theInterceptor); 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 * @param theParameters The parameters to use as input. May also be <code>null</code> if the operation
* does not require any input parameters. * 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 * 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); 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 * 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 * 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. * 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,49 +20,11 @@ package ca.uhn.fhir.rest.client.impl;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isNotBlank; import ca.uhn.fhir.context.*;
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.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.client.api.*;
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.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; 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.client.method.MethodUtil;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.OperationOutcomeUtil; 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 { 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 static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class);
private final IHttpClient myClient; private final IHttpClient myClient;
private final RestfulClientFactory myFactory;
private final String myUrlBase;
private boolean myDontValidateConformance; private boolean myDontValidateConformance;
private EncodingEnum myEncoding = null; // default unspecified (will be XML) private EncodingEnum myEncoding = null; // default unspecified (will be XML)
private final RestfulClientFactory myFactory;
private List<IClientInterceptor> myInterceptors = new ArrayList<IClientInterceptor>(); private List<IClientInterceptor> myInterceptors = new ArrayList<IClientInterceptor>();
private boolean myKeepResponses = false; private boolean myKeepResponses = false;
private IHttpResponse myLastResponse; private IHttpResponse myLastResponse;
private String myLastResponseBody; private String myLastResponseBody;
private Boolean myPrettyPrint = false; private Boolean myPrettyPrint = false;
private SummaryEnum mySummary; private SummaryEnum mySummary;
private final String myUrlBase; private RequestFormatParamStyleEnum myRequestFormatParamStyle = RequestFormatParamStyleEnum.SHORT;
BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) { BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) {
super(); super();
@ -121,10 +96,12 @@ public abstract class BaseClient implements IRestfulClient {
protected Map<String, List<String>> createExtraParams() { protected Map<String, List<String>> createExtraParams() {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>(); HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
if (getEncoding() == EncodingEnum.XML) { if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); if (getEncoding() == EncodingEnum.XML) {
} else if (getEncoding() == EncodingEnum.JSON) { retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); } else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
} }
if (isPrettyPrint()) { if (isPrettyPrint()) {
@ -150,6 +127,17 @@ public abstract class BaseClient implements IRestfulClient {
return myEncoding; 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} * {@inheritDoc}
*/ */
@ -192,10 +180,21 @@ public abstract class BaseClient implements IRestfulClient {
return mySummary; return mySummary;
} }
@Override
public void setSummary(SummaryEnum theSummary) {
mySummary = theSummary;
}
public String getUrlBase() { public String getUrlBase() {
return myUrlBase; 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) { <T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation) {
return invokeClient(theContext, binding, clientInvocation, false); return invokeClient(theContext, binding, clientInvocation, false);
} }
@ -219,10 +218,12 @@ public abstract class BaseClient implements IRestfulClient {
Map<String, List<String>> params = createExtraParams(); Map<String, List<String>> params = createExtraParams();
if (clientInvocation instanceof HttpGetClientInvocation) { if (clientInvocation instanceof HttpGetClientInvocation) {
if (theEncoding == EncodingEnum.XML) { if (myRequestFormatParamStyle == RequestFormatParamStyleEnum.SHORT) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); if (theEncoding == EncodingEnum.XML) {
} else if (theEncoding == EncodingEnum.JSON) { params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); } 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_CACHE, theCacheControlDirective.isNoCache());
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore()); addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
if (theCacheControlDirective.getMaxResults() != null) { 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) { if (b.length() > 0) {
httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString()); httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
@ -397,6 +398,13 @@ public abstract class BaseClient implements IRestfulClient {
return myKeepResponses; 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 * 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 * 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); 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) { private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) {
if (myKeepResponses) { if (myKeepResponses) {
myLastResponse = response; myLastResponse = response;
@ -438,55 +457,12 @@ public abstract class BaseClient implements IRestfulClient {
myDontValidateConformance = theDontValidateConformance; 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 @Override
public void unregisterInterceptor(IClientInterceptor theInterceptor) { public void unregisterInterceptor(IClientInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null"); Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor); 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> { protected final class ResourceResponseHandler<T extends IBaseResource> implements IClientResponseHandler<T> {
private boolean myAllowHtmlResponse; 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") @SuppressWarnings("unchecked")
@Override @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"); Validate.notNull(theOutputParameterType, "theOutputParameterType may not be null");
RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType); RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType);
if (def == null) { if (def == null) {
@ -1307,9 +1307,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
@Override @Override
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) { public IOperationUntypedWithInputAndPartialOutput withParameters(IBaseParameters theParameters) {
Validate.notNull(theParameters, "theParameters can not be null"); Validate.notNull(theParameters, "theParameters can not be null");
myParameters = theParameters; myParameters = theParameters;
myParametersDef = myContext.getResourceDefinition(theParameters.getClass());
return this; return this;
} }
@ -1445,7 +1446,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
OutcomeResponseHandler binding = new OutcomeResponseHandler(myPrefer); 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); return invoke(params, binding, invocation);
} }

View File

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

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jaxrs.server.util; package ca.uhn.fhir.jaxrs.server.util;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
@ -10,6 +11,7 @@ import java.util.Set;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -108,10 +110,24 @@ public class JaxRsResponseTest {
assertEquals("application/xml+fhir; charset=UTF-8", result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); 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() { private Patient createPatient() {
Patient theResource = new Patient(); Patient theResource = new Patient();
theResource.setId(new IdDt(15L)); theResource.setId(new IdDt(15L));
return theResource; return theResource;
} }
} }

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config; 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.cfg.AvailableSettings;
import org.hibernate.query.criteria.LiteralHandlingMode; import org.hibernate.query.criteria.LiteralHandlingMode;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.BaseMethodBinding;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding; import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.*;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@ -86,17 +83,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
@Autowired @Autowired
private DaoRegistry myDaoRegistry; 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) { private void populateEntryWithOperationOutcome(BaseServerResponseException caughtEx, BUNDLEENTRY nextEntry) {
myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry); myVersionAdapter.populateEntryWithOperationOutcome(caughtEx, nextEntry);
} }
@ -164,7 +150,6 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
return defaultString(theId.getValue()).startsWith(URN_PREFIX); return defaultString(theId.getValue()).startsWith(URN_PREFIX);
} }
public void setDao(BaseHapiFhirDao theDao) { public void setDao(BaseHapiFhirDao theDao) {
myDao = 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) { private BUNDLE batch(final RequestDetails theRequestDetails, BUNDLE theRequest) {
ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size()); ourLog.info("Beginning batch with {} resources", myVersionAdapter.getEntries(theRequest).size());
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@ -255,6 +274,7 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
validateDependencies(); validateDependencies();
String transactionType = myVersionAdapter.getBundleType(theRequest); String transactionType = myVersionAdapter.getBundleType(theRequest);
if (org.hl7.fhir.r4.model.Bundle.BundleType.BATCH.toCode().equals(transactionType)) { if (org.hl7.fhir.r4.model.Bundle.BundleType.BATCH.toCode().equals(transactionType)) {
return batch(theRequestDetails, theRequest); return batch(theRequestDetails, theRequest);
} }
@ -846,18 +866,10 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
String getEntryRequestIfNoneMatch(BUNDLEENTRY theEntry); String getEntryRequestIfNoneMatch(BUNDLEENTRY theEntry);
void setResponseOutcome(BUNDLEENTRY theEntry, IBaseOperationOutcome theOperationOutcome); void setResponseOutcome(BUNDLEENTRY theEntry, IBaseOperationOutcome theOperationOutcome);
}
private static class BaseServerResponseExceptionHolder { void setRequestVerb(BUNDLEENTRY theEntry, String theVerb);
private BaseServerResponseException myException;
public BaseServerResponseException getException() { void setRequestUrl(BUNDLEENTRY theEntry, String theUrl);
return myException;
}
public void setException(BaseServerResponseException myException) {
this.myException = myException;
}
} }
/** /**
@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.Date;
import java.util.List; import java.util.List;
@ -150,4 +150,14 @@ public class TransactionProcessorVersionAdapterR4 implements TransactionProcesso
theEntry.getResponse().setOutcome((Resource) theOperationOutcome); 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.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.ResourceTag;
import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.primitive.IdDt; 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.Constants;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; 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 * See #410
*/ */
@ -3040,7 +3056,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
@Test @Test
public void testTransactionWithReplacement() { 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 binary = new Binary();
binary.setId(IdType.newRandomUuid()); binary.setId(IdType.newRandomUuid());

View File

@ -21,9 +21,7 @@ package ca.uhn.fhir.rest.server;
*/ */
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.*;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
@ -35,7 +33,7 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
private IIdType myOperationResourceId; private IIdType myOperationResourceId;
private IPrimitiveType<Date> myOperationResourceLastUpdated; private IPrimitiveType<Date> myOperationResourceLastUpdated;
private ConcurrentHashMap<String, String> theHeaders = new ConcurrentHashMap<String, String>(); private Map<String, List<String>> theHeaders = new HashMap<>();
private T theRequestDetails; private T theRequestDetails;
public RestfulResponse(T requestDetails) { public RestfulResponse(T requestDetails) {
@ -44,14 +42,14 @@ public abstract class RestfulResponse<T extends RequestDetails> implements IRest
@Override @Override
public void addHeader(String headerKey, String headerValue) { 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 * Get the http headers
* @return the headers * @return the headers
*/ */
public ConcurrentHashMap<String, String> getHeaders() { public Map<String, List<String>> getHeaders() {
return theHeaders; 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.OutputStreamWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.Writer; import java.io.Writer;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -75,8 +76,18 @@ public class ServletRestfulResponse extends RestfulResponse<ServletRequestDetail
private void addHeaders() { private void addHeaders() {
HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse(); HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
getRequestDetails().getServer().addHeadersToResponse(theHttpResponse); getRequestDetails().getServer().addHeadersToResponse(theHttpResponse);
for (Entry<String, String> header : getHeaders().entrySet()) { for (Entry<String, List<String>> header : getHeaders().entrySet()) {
theHttpResponse.setHeader(header.getKey(), header.getValue()); 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 // nothing
} }
@Override
public void setFormatParamStyle(RequestFormatParamStyleEnum theRequestFormatParamStyle) {
// nothing
}
@Override @Override
public EncodingEnum getEncoding() { public EncodingEnum getEncoding() {
// TODO Auto-generated method stub // TODO Auto-generated method stub

View File

@ -5,7 +5,12 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.Date; 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.hamcrest.core.StringContains;
import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Coding;
@ -28,6 +33,8 @@ import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.templateresource.ITemplateResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException; 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 @Test
public void testGenerateDiagnosticReport() throws DataFormatException { 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; package ca.uhn.fhir.rest.client;
import ca.uhn.fhir.context.FhirContext; 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.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome; 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.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.util.RandomServerPortProvider; import ca.uhn.fhir.util.RandomServerPortProvider;
import ca.uhn.fhir.util.TestUtil; 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.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
@ -20,9 +16,7 @@ import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -40,8 +34,9 @@ public class ClientHeadersR4Test {
private static Server ourServer; private static Server ourServer;
private static String ourServerBase; private static String ourServerBase;
private static HashMap<String, List<String>> ourHeaders; private static HashMap<String, List<String>> ourHeaders;
private static IGenericClient ourClient; private static HashMap<String, String[]> ourParams;
private static String ourMethod; private static String ourMethod;
private IGenericClient myClient;
@Before @Before
public void before() { public void before() {
@ -49,34 +44,125 @@ public class ClientHeadersR4Test {
ourMethod = null; 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 @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(); final Patient resp1 = new Patient();
resp1.setActive(true); resp1.setActive(true);
MethodOutcome resp = ourClient.create().resource(resp1).execute(); MethodOutcome resp = myClient.create().resource(resp1).execute();
assertNotNull(resp); assertNotNull(resp);
assertEquals(1, ourHeaders.get(Constants.HEADER_CONTENT_TYPE).size()); assertEquals(1, ourHeaders.get(Constants.HEADER_CONTENT_TYPE).size());
assertEquals("application/fhir+xml; charset=UTF-8", ourHeaders.get(Constants.HEADER_CONTENT_TYPE).get(0)); 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 @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();
@ -94,7 +180,6 @@ public class ClientHeadersR4Test {
ourServerBase = "http://localhost:" + myPort + "/fhir/context"; ourServerBase = "http://localhost:" + myPort + "/fhir/context";
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ServletHolder servletHolder = new ServletHolder(); ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(new TestServlet()); 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; package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertEquals; import ca.uhn.fhir.context.FhirContext;
import static org.junit.Assert.fail; import ca.uhn.fhir.rest.annotation.Operation;
import static org.mockito.Mockito.mock; import ca.uhn.fhir.rest.annotation.OperationParam;
import static org.mockito.Mockito.when; 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.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import org.apache.commons.io.IOUtils; import static org.junit.Assert.assertEquals;
import org.apache.commons.io.input.ReaderInputStream; import static org.junit.Assert.fail;
import org.apache.http.HttpResponse; import static org.mockito.Mockito.mock;
import org.apache.http.ProtocolVersion; import static org.mockito.Mockito.when;
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;
public class OperationClientR4Test { public class OperationClientR4Test {
@ -48,13 +52,6 @@ public class OperationClientR4Test {
private ArgumentCaptor<HttpUriRequest> capt; private ArgumentCaptor<HttpUriRequest> capt;
private IGenericClient ourGenClient; private IGenericClient ourGenClient;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Before @Before
public void before() throws Exception { public void before() throws Exception {
ourCtx = FhirContext.forR4(); ourCtx = FhirContext.forR4();
@ -64,7 +61,7 @@ public class OperationClientR4Test {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
Parameters outParams = new Parameters(); Parameters outParams = new Parameters();
outParams.addParameter().setName("FOO"); outParams.addParameter().setName("FOO");
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams); 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().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() { when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override @Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable { public InputStream answer(InvocationOnMock theInvocation) {
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8")); return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
} }
}); });
@ -95,10 +92,10 @@ public class OperationClientR4Test {
.execute(); .execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val")); Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName()); assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0); HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent(), Charsets.UTF_8); String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(value.getEntity().getContent());
ourLog.info(requestBody); ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
@ -110,7 +107,7 @@ public class OperationClientR4Test {
} }
@Test @Test
public void testNonRepeatingGenericUsingUrl() throws Exception { public void testNonRepeatingGenericUsingUrl() {
ourGenClient ourGenClient
.operation() .operation()
.onServer() .onServer()
@ -121,14 +118,31 @@ public class OperationClientR4Test {
.execute(); .execute();
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val")); Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName()); assertEquals("FOO", response.getParameter().get(0).getName());
HttpGet value = (HttpGet) capt.getAllValues().get(0); HttpGet value = (HttpGet) capt.getAllValues().get(0);
assertEquals("http://foo/$nonrepeating?valstr=str&valtok=sys2%7Cval2", value.getURI().toASCIIString()); 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 @Test
public void testOperationOnInstanceWithIncompleteInstanceId() throws Exception { public void testOperationOnInstanceWithIncompleteInstanceId() {
try { try {
ourGenClient ourGenClient
.operation() .operation()
@ -146,10 +160,10 @@ public class OperationClientR4Test {
public void testNonRepeatingUsingParameters() throws Exception { public void testNonRepeatingUsingParameters() throws Exception {
Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val")); Parameters response = ourAnnClient.nonrepeating(new StringParam("str"), new TokenParam("sys", "val"));
assertEquals("FOO", response.getParameter().get(0).getName()); assertEquals("FOO", response.getParameter().get(0).getName());
HttpPost value = (HttpPost) capt.getAllValues().get(0); HttpPost value = (HttpPost) capt.getAllValues().get(0);
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent(), Charsets.UTF_8); String requestBody = IOUtils.toString(value.getEntity().getContent(), Charsets.UTF_8);
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent()); IOUtils.closeQuietly(value.getEntity().getContent());
ourLog.info(requestBody); ourLog.info(requestBody);
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody); Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString()); assertEquals("http://foo/$nonrepeating", value.getURI().toASCIIString());
@ -160,48 +174,52 @@ public class OperationClientR4Test {
assertEquals("sys|val", ((StringType) request.getParameter().get(1).getValue()).getValue()); 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) @Operation(name = "$andlist", idempotent = true)
public Parameters andlist( Parameters andlist(
//@formatter:off //@formatter:off
@OperationParam(name="valstr", max=10) StringAndListParam theValStr, @OperationParam(name = "valstr", max = 10) StringAndListParam theValStr,
@OperationParam(name="valtok", max=10) TokenAndListParam theValTok @OperationParam(name = "valtok", max = 10) TokenAndListParam theValTok
//@formatter:on //@formatter:on
); );
@Operation(name = "$andlist-withnomax", idempotent = true) @Operation(name = "$andlist-withnomax", idempotent = true)
public Parameters andlistWithNoMax( Parameters andlistWithNoMax(
//@formatter:off //@formatter:off
@OperationParam(name="valstr") StringAndListParam theValStr, @OperationParam(name = "valstr") StringAndListParam theValStr,
@OperationParam(name="valtok") TokenAndListParam theValTok @OperationParam(name = "valtok") TokenAndListParam theValTok
//@formatter:on //@formatter:on
); );
@Operation(name = "$nonrepeating", idempotent = true) @Operation(name = "$nonrepeating", idempotent = true)
public Parameters nonrepeating( Parameters nonrepeating(
//@formatter:off //@formatter:off
@OperationParam(name="valstr") StringParam theValStr, @OperationParam(name = "valstr") StringParam theValStr,
@OperationParam(name="valtok") TokenParam theValTok @OperationParam(name = "valtok") TokenParam theValTok
//@formatter:on //@formatter:on
); );
@Operation(name = "$orlist", idempotent = true) @Operation(name = "$orlist", idempotent = true)
public Parameters orlist( Parameters orlist(
//@formatter:off //@formatter:off
@OperationParam(name="valstr", max=10) List<StringOrListParam> theValStr, @OperationParam(name = "valstr", max = 10) List<StringOrListParam> theValStr,
@OperationParam(name="valtok", max=10) List<TokenOrListParam> theValTok @OperationParam(name = "valtok", max = 10) List<TokenOrListParam> theValTok
//@formatter:on //@formatter:on
); );
@Operation(name = "$orlist-withnomax", idempotent = true) @Operation(name = "$orlist-withnomax", idempotent = true)
public Parameters orlistWithNoMax( Parameters orlistWithNoMax(
//@formatter:off //@formatter:off
@OperationParam(name="valstr") List<StringOrListParam> theValStr, @OperationParam(name = "valstr") List<StringOrListParam> theValStr,
@OperationParam(name="valtok") List<TokenOrListParam> theValTok @OperationParam(name = "valtok") List<TokenOrListParam> theValTok
//@formatter:on //@formatter:on
); );
} }
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
} }

View File

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

View File

@ -164,6 +164,19 @@
now possible to disallow this action, to only allow alphanumeric IDs (the default now possible to disallow this action, to only allow alphanumeric IDs (the default
and only option previously) or allow any IDs including alphanumeric. and only option previously) or allow any IDs including alphanumeric.
</action> </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>
<release version="3.5.0" date="2018-09-17"> <release version="3.5.0" date="2018-09-17">