Merge remote-tracking branch 'remotes/origin/master' into im_20200316_lastn_operation_elasticsearch

This commit is contained in:
ianmarshall 2020-03-31 13:30:28 -04:00
commit f891634341
94 changed files with 2076 additions and 291 deletions

17
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Two years until issues go stale
daysUntilStale: 730
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@ -3,7 +3,7 @@ package ca.uhn.fhir.context;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
@ -625,12 +625,21 @@ public class FhirContext {
}
/**
* Creates a new FluentPath engine which can be used to exvaluate
* @since 2.2
* @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead.
*/
@Deprecated
public IFhirPath newFluentPath() {
return newFhirPath();
}
/**
* Creates a new FhirPath engine which can be used to evaluate
* path expressions over FHIR resources. Note that this engine will use the
* {@link IValidationSupport context validation support} module which is
* configured on the context at the time this method is called.
* <p>
* In other words, call {@link #setValidationSupport(IValidationSupport)} before
* In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before
* calling {@link #newFluentPath()}
* </p>
* <p>
@ -640,10 +649,10 @@ public class FhirContext {
* {@link UnsupportedOperationException}
* </p>
*
* @since 2.2
* @since 5.0.0
*/
public IFluentPath newFluentPath() {
return myVersion.createFluentPathExecutor(this);
public IFhirPath newFhirPath() {
return myVersion.createFhirPathExecutor(this);
}
/**

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.fluentpath;
package ca.uhn.fhir.fhirpath;
/*
* #%L
@ -23,18 +23,18 @@ package ca.uhn.fhir.fluentpath;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
* This exception is thrown if a FluentPath expression can not be executed successfully
* This exception is thrown if a FHIRPath expression can not be executed successfully
* for any reason
*/
public class FluentPathExecutionException extends InternalErrorException {
public class FhirPathExecutionException extends InternalErrorException {
private static final long serialVersionUID = 1L;
public FluentPathExecutionException(Throwable theCause) {
public FhirPathExecutionException(Throwable theCause) {
super(theCause);
}
public FluentPathExecutionException(String theMessage) {
public FhirPathExecutionException(String theMessage) {
super(theMessage);
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.fluentpath;
package ca.uhn.fhir.fhirpath;
/*
* #%L
@ -25,7 +25,7 @@ import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase;
public interface IFluentPath {
public interface IFhirPath {
/**
* Apply the given FluentPath expression against the given input and return

View File

@ -27,7 +27,12 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import javax.annotation.Nonnull;
import java.util.*;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Value for {@link Hook#value()}
@ -374,6 +379,44 @@ public enum Pointcut {
),
/**
* <b>Server Hook:</b>
* This method is called when a stream writer is generated that will be used to stream a non-binary response to
* a client. Hooks may return a wrapped writer which adds additional functionality as needed.
*
* <p>
* Hooks may accept the following parameters:
* <ul>
* <li>
* java.io.Writer - The response writing Writer. Typically a hook will wrap this writer and layer additional functionality
* into the wrapping writer.
* </li>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request.
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* </ul>
* </p>
* <p>
* Hook methods should return a {@link Writer} instance that will be used to stream the response. Hook methods
* should not throw any exception.
* </p>
*
* @since 5.0.0
*/
SERVER_OUTGOING_WRITER_CREATED(Writer.class,
"java.io.Writer",
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
/**
* <b>Server Hook:</b>
@ -1643,12 +1686,12 @@ public enum Pointcut {
* </p>
* <p>
* THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING.
* </p>
* <p>
* </p>
* <p>
* Note that this is a performance tracing hook. Use with caution in production
* systems, since calling it may (or may not) carry a cost.
* </p>
* <p>
* </p>
* <p>
* Hooks may accept the following parameters:
* </p>
* <ul>
@ -1722,9 +1765,7 @@ public enum Pointcut {
* This pointcut is used only for unit tests. Do not use in production code as it may be changed or
* removed at any time.
*/
TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName())
;
TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName());
private final List<String> myParameterTypes;
private final Class<?> myReturnType;

View File

@ -23,10 +23,10 @@ package ca.uhn.fhir.model.api;
import java.io.InputStream;
import java.util.Date;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
/**
@ -38,7 +38,7 @@ import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
*/
public interface IFhirVersion {
IFluentPath createFluentPathExecutor(FhirContext theFhirContext);
IFhirPath createFhirPathExecutor(FhirContext theFhirContext);
IBaseResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase);

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBase;
@ -120,7 +120,7 @@ public abstract class BaseNarrativeGenerator implements INarrativeGenerator {
if (theFhirContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
return Collections.singletonList(theResource);
}
IFluentPath fhirPath = theFhirContext.newFluentPath();
IFhirPath fhirPath = theFhirContext.newFluentPath();
return fhirPath.evaluate(theResource, theContextPath, IBase.class);
}

View File

@ -317,19 +317,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
case CONTAINED_RESOURCE_LIST:
case CONTAINED_RESOURCES: {
/*
* Disabled per #103 ContainedDt value = (ContainedDt) theNextValue; for (IResource next :
* value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
* encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true,
* fixContainedResourceId(next.getId().getValue())); }
*/
List<IBaseResource> containedResources = getContainedResources().getContainedResources();
if (containedResources.size() > 0) {
beginArray(theEventWriter, theChildName);
for (IBaseResource next : containedResources) {
IIdType resourceId = getContainedResources().getResourceId(next);
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
String value = resourceId.getValue();
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(value), theEncodeContext);
}
theEventWriter.endArray();
@ -359,7 +354,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
theEncodeContext.pushPath(def.getName(), true);
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, theEncodeContext);
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext);
theEncodeContext.popPath();
break;

View File

@ -1074,7 +1074,7 @@ class ParserState<T> {
*/
for (IBaseResource next : myGlobalResources) {
IIdType id = next.getIdElement();
if (id != null && id.isEmpty() == false) {
if (id != null && !id.isEmpty()) {
String resName = myContext.getResourceDefinition(next).getName();
IIdType idType = id.withResourceType(resName).toUnqualifiedVersionless();
idToResource.put(idType.getValueAsString(), next);
@ -1082,10 +1082,11 @@ class ParserState<T> {
}
for (IBaseReference nextRef : myGlobalReferences) {
if (nextRef.isEmpty() == false && nextRef.getReferenceElement() != null) {
if (!nextRef.isEmpty() && nextRef.getReferenceElement() != null) {
IIdType unqualifiedVersionless = nextRef.getReferenceElement().toUnqualifiedVersionless();
IBaseResource target = idToResource.get(unqualifiedVersionless.getValueAsString());
if (target != null) {
// resource can already be filled with local contained resource by populateTarget()
if (target != null && nextRef.getResource() == null) {
nextRef.setResource(target);
}
}

View File

@ -287,7 +287,8 @@ public class XmlParser extends BaseParser {
for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
String value = resourceId.getValue();
encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext);
theEventWriter.writeEndElement();
}
break;
@ -300,7 +301,7 @@ public class XmlParser extends BaseParser {
}
theEventWriter.writeStartElement(theChildName);
theEncodeContext.pushPath(resourceName, true);
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext);
encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.writeEndElement();
break;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser.json.jackson;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 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 ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.json.JsonLikeArray;
import ca.uhn.fhir.parser.json.JsonLikeObject;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.parser.json.jackson;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 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 ca.uhn.fhir.parser.json.JsonLikeWriter;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;

View File

@ -259,6 +259,8 @@ public class Constants {
* </p>
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
public static final String PARAM_FHIRPATH = "_fhirpath";
public static final String PARAM_TYPE = "_type";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;

View File

@ -200,6 +200,13 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/
return myValue;
}
/**
* Note that the parameter to this method <b>must</b> be a resource reference, e.g
* <code>123</code> or <code>Patient/123</code> or <code>http://example.com/fhir/Patient/123</code>
* or something like this. This is not appropriate for cases where a chain is being used and
* the value is for a different type of parameter (e.g. a token). In that case, use one of the
* setter constructors.
*/
public ReferenceParam setValue(String theValue) {
IdDt id = new IdDt(theValue);
String qualifier= null;

View File

@ -238,7 +238,7 @@ public class ParametersUtil {
addPart(theContext, theParameter, theName, coding);
}
private static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
BaseRuntimeChildDefinition partChild = def.getChildByName("part");
@ -252,4 +252,19 @@ public class ParametersUtil {
partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
}
public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) theContext.getElementDefinition(theParameter.getClass());
BaseRuntimeChildDefinition partChild = def.getChildByName("part");
BaseRuntimeElementCompositeDefinition<?> partChildElem = (BaseRuntimeElementCompositeDefinition<?>) partChild.getChildByName("part");
IBase part = partChildElem.newInstance();
partChild.getMutator().addValue(theParameter, part);
IPrimitiveType<String> name = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
name.setValue(theName);
partChildElem.getChildByName("name").getMutator().addValue(part, name);
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
}
}

View File

@ -135,3 +135,6 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can no
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"

View File

@ -25,7 +25,11 @@ import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.helger.commons.io.file.FileHelper;
import org.apache.commons.cli.*;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
import org.fusesource.jansi.Ansi;
@ -43,11 +47,12 @@ import static org.fusesource.jansi.Ansi.ansi;
@SuppressWarnings("WeakerAccess")
public abstract class BaseApp {
protected static final org.slf4j.Logger ourLog;
static final String LINESEP = System.getProperty("line.separator");
private static final String STACKFILTER_PATTERN = "%xEx{full, sun.reflect, org.junit, org.eclipse, java.lang.reflect.Method, org.springframework, org.hibernate, com.sun.proxy, org.attoparser, org.thymeleaf}";
private static final String STACKFILTER_PATTERN_PROP = "log.stackfilter.pattern";
static final String LINESEP = System.getProperty("line.separator");
protected static final org.slf4j.Logger ourLog;
private static List<BaseCommand> ourCommands;
private static boolean ourDebugMode;
static {
System.setProperty(STACKFILTER_PATTERN_PROP, STACKFILTER_PATTERN);
@ -115,13 +120,19 @@ public abstract class BaseApp {
System.out.println("Options:");
HelpFormatter fmt = new HelpFormatter();
PrintWriter pw = new PrintWriter(System.out);
fmt.printOptions(pw, columns, theCommand.getOptions(), 2, 2);
fmt.printOptions(pw, columns, getOptions(theCommand), 2, 2);
pw.flush();
// That's it!
System.out.println();
}
private Options getOptions(BaseCommand theCommand) {
Options options = theCommand.getOptions();
options.addOption(null, "debug", false, "Enable debug mode");
return options;
}
private void logUsage() {
logAppHeader();
System.out.println("Usage:");
@ -232,7 +243,7 @@ public abstract class BaseApp {
myShutdownHook = new MyShutdownHook(command);
Runtime.getRuntime().addShutdownHook(myShutdownHook);
Options options = command.getOptions();
Options options = getOptions(command);
DefaultParser parser = new DefaultParser();
CommandLine parsedOptions;
@ -247,6 +258,11 @@ public abstract class BaseApp {
throw new ParseException("Unrecognized argument: " + parsedOptions.getArgList().get(0));
}
if (parsedOptions.hasOption("debug")) {
loggingConfigOnDebug();
ourDebugMode = true;
}
// Actually execute the command
command.run(parsedOptions);
@ -290,7 +306,7 @@ public abstract class BaseApp {
private void exitDueToException(Throwable e) {
if ("true".equals(System.getProperty("test"))) {
if (e instanceof CommandFailureException) {
throw (CommandFailureException)e;
throw (CommandFailureException) e;
}
throw new Error(e);
} else {
@ -316,6 +332,24 @@ public abstract class BaseApp {
}
}
private class MyShutdownHook extends Thread {
private final BaseCommand myFinalCommand;
MyShutdownHook(BaseCommand theFinalCommand) {
myFinalCommand = theFinalCommand;
}
@Override
public void run() {
ourLog.info(provideProductName() + " is shutting down...");
myFinalCommand.cleanup();
}
}
public static boolean isDebugMode() {
return ourDebugMode;
}
private static void loggingConfigOff() {
try {
JoranConfigurator configurator = new JoranConfigurator();
@ -337,18 +371,16 @@ public abstract class BaseApp {
}
}
private class MyShutdownHook extends Thread {
private final BaseCommand myFinalCommand;
MyShutdownHook(BaseCommand theFinalCommand) {
myFinalCommand = theFinalCommand;
private static void loggingConfigOnDebug() {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on-debug.xml"));
} catch (JoranException e) {
e.printStackTrace();
}
@Override
public void run() {
ourLog.info(provideProductName() + " is shutting down...");
myFinalCommand.cleanup();
}
ourLog.info("Debug logging is enabled");
}
}

View File

@ -0,0 +1,53 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<useJansi>false</useJansi>
<encoder>
<pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>output.log</file>
<append>false</append>
<encoder>
<pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n</pattern>
</encoder>
<Encoding>utf-8</Encoding>
</appender>
<logger name="ca.uhn.fhir" additivity="false" level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<logger name="ca.cdr" additivity="false" level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<!-- These two are used by the websocket client -->
<logger name="websocket.RECV" additivity="false" level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<logger name="websocket.SEND" additivity="false" level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<!-- These two are used by SynchronizeFhirServersCommand -->
<logger name="sync.SOURCE" additivity="false" level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<logger name="sync.TARGET" additivity="false" level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>

View File

@ -85,6 +85,16 @@
<artifactId>Saxon-HE</artifactId>
</dependency>
<!--
Gson is needed for some utility classes in org.hl7.fhir.convertors, so
this is necessary for the JavaDocs to build
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<!-- Testing -->
<dependency>
<groupId>ch.qos.logback</groupId>

View File

@ -32,11 +32,11 @@ import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.util.Arrays;
@SuppressWarnings("serial")
@SuppressWarnings({"serial", "RedundantThrows", "InnerClassMayBeStatic"})
public class ServletExamples {
// START SNIPPET: loggingInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithLogging extends RestfulServer {
@Override
@ -121,6 +121,24 @@ public class ServletExamples {
}
// END SNIPPET: exceptionInterceptor
// START SNIPPET: fhirPathInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithFhirPath extends RestfulServer {
@Override
protected void initialize() throws ServletException {
// ... define your resource providers here ...
// Now register the interceptor
FhirPathFilterInterceptor interceptor = new FhirPathFilterInterceptor();
registerInterceptor(interceptor);
}
}
// END SNIPPET: fhirPathInterceptor
// START SNIPPET: responseHighlighterInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithResponseHighlighter extends RestfulServer {

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1763
title: In servers, when requesting _summary=count, the response Bundle.type value was filtered, leading
to an invalid response bundle. This has been corrected. Thanks to GitHub user @Legi429 for reporting!

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1769
title: A new built-in server interceptor called FhirPathFilterInterceptor has been added. This interceptor
evaluates an arbitrary FHIRPath expression against the resource being returned and replaces the response
with a Parameters resource containing the results of the evaluation.

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1770
title: When parsing Bundles, contained resoures from other entries in the Bundle could incorrecly be
stitched into a target resource if they had the same local ID. Thanks to August Langhout for
the pull request!

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1772
title: "The JPA server now allows chained searches on the `_type` parameter. For example, the following
could be used to find all Encounters with a context of type Group: `Encounter?subject._type=Group`."

View File

@ -0,0 +1,6 @@
---
type: add
issue: 1776
title: A new server interceptor called `ResponseSizeCapturingInterceptor` has been added. This interceptor captures and makes
available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response
stream for FHIR responses.

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1778
title: "When encoding a resource, a crash could occur if the resource had a contained Bundle resource. This
is not commonly done, but there are valid scenarios for doing so."

View File

@ -17,3 +17,11 @@
[Migrating to HAPI FHIR 5.x](/hapi-fhir/docs/validation/instance_validator.html#migrating-to-hapi-fhir-5x)
for details on how to account for this change in your code.
"
- item:
issue: "1769"
type: "change"
title: "**Breaking Change**:
The `IFluentPath` interface has been renamed to `IFhirPath`, and the `FhirContext#newFluentPath()` method
has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially
called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming.
"

View File

@ -62,5 +62,5 @@ To enable detailed logging of client requests and responses (what URL is being r
# Server Request Logging
To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptr](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information.
To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptor](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information.

View File

@ -5,7 +5,7 @@ HAPI also provides a second style of client, called the *annotation-driven* clie
The design of the annotation-driven client is intended to be similar to that of JAX-WS, so users of that specification should be comfortable with this one. It uses a user-defined interface containing special annotated methods which HAPI binds to calls against a server.
The annotation-driven client is particularly useful if you have a server that exposes a set of specific operations (search parameter combinations, named queries, etc.) and you want to let developers have a stongly/statically typed interface to that server.
The annotation-driven client is particularly useful if you have a server that exposes a set of specific operations (search parameter combinations, named queries, etc.) and you want to let developers have a strongly/statically typed interface to that server.
There is no difference in terms of capability between the two styles of client. There is simply a difference in programming style and complexity. It is probably safe to say that the generic client is easier to use and leads to more readable code, at the expense of not giving any visibility into the specific capabilities of the server you are interacting with.
@ -40,7 +40,7 @@ Once your client interface is created, all that is left is to create a FhirConte
Restful client interfaces that you create will also extend the interface [IRestfulClient](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClient.html), which comes with some helpful methods for configuring the way that the client will interact with the server.
The following snippet shows how to configure the cliet to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently).
The following snippet shows how to configure the client to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently).
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|clientConfig}}

View File

@ -10,7 +10,7 @@ This page outlines ways that the client can be configured for specific behaviour
By default, the client will query the server before the very first operation to download the server's conformance/metadata statement and verify that the server is appropriate for the given client. This check is only done once per server endpoint for a given FhirContext.
This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unneccesary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check.
This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unnecessary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|dontValidate}}
@ -36,7 +36,7 @@ REST clients (both Generic and Annotation-Driven) use [Apache HTTP Client](http:
The Apache HTTP Client is very powerful and extremely flexible, but can be confusing at first to configure, because of the low-level approach that the library uses.
In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timesouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library.
In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timeouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library.
This configuration is provided by accessing the [IRestfulClientFactory](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClientFactory.html) class from the FhirContext.
@ -62,7 +62,7 @@ The following example shows how to configure the use of an HTTP proxy in the cli
As of HAPI FHIR 2.0, an alternate client implementation is available. This client replaces the low-level Apache HttpClient implementation with the Square [OkHttp](http://square.github.io/okhttp/) library.
Changing HTTP implementations should be mostly ransparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library.
Changing HTTP implementations should be mostly transparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library.
Note that as of HAPI FHIR 2.1, OkHttp is the default provider on Android, and will be used without any configuration being required. This is done because HttpClient is deprecated on Android and has caused problems in the past.

View File

@ -166,7 +166,7 @@ The server responds with the following response. Note that the ID of the already
# Fetch all Pages of a Bundle
This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the resuts.
This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the results.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleFetcher.java|loadAll}}

View File

@ -22,7 +22,7 @@ Note that most fluent operations end with an `execute()` statement which actuall
# Search
Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to proide a complete implementation of the FHIR API search specification via the generic client API.
Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to provide a complete implementation of the FHIR API search specification via the generic client API.
## Search - By Type
@ -66,7 +66,7 @@ If the server supports paging results, the client has a page method which can be
## Search - Composite Parameters
If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being seached. The following example shows the syntax.
If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being searched. The following example shows the syntax.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|searchComposite}}
@ -193,7 +193,7 @@ The following example shows how to perform an update operation using the generic
## Conditional Updates
FHIR also specifies a type of update called "conditional updates", where insetad of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work.
FHIR also specifies a type of update called "conditional updates", where instead of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|updateConditional}}
@ -233,7 +233,7 @@ To retrieve the server's capability statement, simply call the [`capabilities()`
# Extended Operations
FHIR also supports a set of *extended operatioons*, which are operatons beyond the basic CRUD operations defined in the specificiation. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back.
FHIR also supports a set of *extended operations*, which are operations beyond the basic CRUD operations defined in the specification. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back.
To invoke an operation using the client, you simply need to create the input [Parameters](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/model/Parameters.html) resource, then pass that to the [`operation()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#operation()) fluent method.

View File

@ -1,5 +1,5 @@
# Getting Started with the Client
A starter project containing all nedded dependencies and some starter code for the HAPI FHIR client is available here:
A starter project containing all needed dependencies and some starter code for the HAPI FHIR client is available here:
* [hapi-fhirstarters-client-skeleton](https://github.com/FirelyTeam/fhirstarters/tree/master/java/hapi-fhirstarters-client-skeleton/)

View File

@ -34,7 +34,7 @@ page.server_plain.rest_operations_search=REST Operations: Search
page.server_plain.rest_operations_operations=REST Operations: Extended Operations
page.server_plain.paging=Paging Search Results
page.server_plain.web_testpage_overlay=Web Testpage Overlay
page.server_plain.multitenency=Multitenency
page.server_plain.multitenancy=Multitenancy
page.server_plain.jax_rs=JAX-RS Support
section.server_jpa.title=JPA Server

View File

@ -26,12 +26,12 @@ This interceptor will then produce output similar to the following:
# Response Customizing: Syntax Highlighting
The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get nice formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works.
The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get a nicely formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works.
* [ResponseHighlighterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html)
* [ResponseHighlighterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java)
To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see no that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the respnose will simply by raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript).
To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see in that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the response will simply be raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript).
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|responseHighlighterInterceptor}}
@ -50,6 +50,52 @@ The following example shows how to register the ExceptionHandlingInterceptor.
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}}
```
# Response Customizing: Evaluate FHIRPath
The FhirPathFilterInterceptor looks for a request URL parameter in the form `_fhirpath=(expression)` in all REST requests. If this parameter is found, the value is treated as a [FHIRPath](http://hl7.org/fhirpath/) expression. The response resource will be replaced with a [Parameters](http://hl7.org/fhir/parameters.html) resource containing the results of the given expression applied against the response resource.
* [FhirPathFilterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.html)
* [FhirPathFilterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java)
The following example shows how to register the ExceptionHandlingInterceptor.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}}
```
An example URL to invoke this function is shown below:
```url
https://hapi.fhir.org/baseR4/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true
```
A sample response to this query is shown below:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "result",
"part": [ {
"name": "expression",
"valueString": "Bundle.entry.resource.as(Patient).name"
}, {
"name": "result",
"valueHumanName": {
"family": "Simpson",
"given": [ "Homer", "Jay" ]
}
}, {
"name": "result",
"valueHumanName": {
"family": "Simpson",
"given": [ "Grandpa" ]
}
} ]
} ]
}
```
# Validation: Request and Response Validation
HAPI FHIR provides a pair of interceptors that can be used to validate incoming requests received by the server, as well as outgoing responses generated by the server.
@ -108,3 +154,10 @@ If you wish to override the value of `Resource.meta.source` using the value supp
* [CaptureResourceSourceFromHeaderInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html)
* [CaptureResourceSourceFromHeaderInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.java)
# Utility: ResponseSizeCapturingInterceptor
The ResponseSizeCapturingInterceptor can be used to capture the number of characters written in each HTTP FHIR response.
* [ResponseSizeCapturingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.html)
* [ResponseSizeCapturingInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java)

View File

@ -2,7 +2,7 @@
HAPI FHIR 3.8.0 introduced a new interceptor framework that is used across the entire library. In previous versions of HAPI FHIR, a "Server Interceptor" framework existed and a separate "Client Interceptor" framework existed. These have now been combined into a single unified (and much more powerful) framework.
Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed do be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor.
Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed to be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor.
The following example shows a very simple interceptor example.

View File

@ -59,7 +59,7 @@ compile 'ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${project.version}'
The HAPI FHIR project generally releases a new full software release 4 times per year, or approximately every 3 months.
Generally speaking it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version.
Generally speaking, it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version.
## Using Snapshot Builds
@ -69,8 +69,6 @@ Using a snapshot build generally involves appending *-SNAPSHOT* to the version n
### Using Maven:
To use a snapshot build, you
```xml
<repositories>
<repository>
@ -109,7 +107,7 @@ XML processing (for resource marshalling and unmarshalling) uses the Java StAX A
Upon starting up, HAPI will emit a log line indicating which StAX implementation is being used, e.g:
```
08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-phrocessor' version '4.4.0'
08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-processor' version '4.4.0'
```
Although most testing is done using the Woodstox implementation of StAX, it is not required and HAPI should work correctly with any compliant implementation of StAX.

View File

@ -26,7 +26,7 @@ Note also that after the release of the FHIR DSTU2 specification, the FHIR
</thead>
<tbody>
<tr>
<td>HAPI FHIR 4.2.0-SNAPSHOT</td>
<td>HAPI FHIR 4.2.0</td>
<td>JDK8</td>
<td class="versions-table-cell-empty"></td>
<td class="versions-table-cell-draft">1.0.2</td>

View File

@ -6,7 +6,7 @@ A built in parser can be used to convert HAPI FHIR Java objects into a serialize
# Parsing (aka Deserializing)
As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to parse.
As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to parse.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|parsing}}
@ -14,7 +14,7 @@ As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/ap
# Encoding (aka Serializing)
As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to serialize.
As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to serialize.
The following example shows a JSON Parser being used to serialize a FHIR resource.
@ -24,7 +24,7 @@ The following example shows a JSON Parser being used to serialize a FHIR resourc
## Pretty Printing
By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed outout:
By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed output:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingPretty}}

View File

@ -135,7 +135,7 @@ This will give the following output:
</Patient>
```
Note that you may also "contain" resources manually in your own code if you prefer. The following example show how to do this:
Note that you may also "contain" resources manually in your own code if you prefer. The following example shows how to do this:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ResourceRefs.java|manualContained}}
@ -147,7 +147,7 @@ By default, HAPI will strip resource versions from references between resources.
This is because in most circumstances, references between resources should be versionless (e.g. the reference just points to the latest version, whatever version that might be).
There are valid circumstances however for wanting versioned references. If you need HAPI to emit versionned references, you have a few options:
There are valid circumstances however for wanting versioned references. If you need HAPI to emit versioned references, you have a few options:
You can force the parser to never strip versions:

View File

@ -48,7 +48,7 @@ Note that in previous revisions of HAPI FHIR documentation we recommended using
The following examples show how to use the Apache Tomcat CorsFilter to enable CORS support. The filter being used (`org.apache.catalina.filters.CorsFilter`) is bundled with Apache Tomcat so if you are deploying to that server you can use the filter.
Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for how to configure a different CORS filter, please send it our way! Examples are always useful!)
Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for configuring a different CORS filter, please send it our way! Examples are always useful!)
In your web.xml file (within the WEB-INF directory in your WAR file), the following filter definition adds the CORS filter, including support for the X-FHIR-Starter header defined by SMART Platforms.

View File

@ -2,10 +2,10 @@
The HAPI FHIR Plain Server ([RestfulServer](/hapi-fhir/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/RestfulServer.html)) is implemented as a standard JEE Servlet, meaning that it can be deployed in any compliant JEE web container.
For users who already have an existing JAX-RS infrastructure, and who would like to use that technology foor their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html).
For users who already have an existing JAX-RS infrastructure, and who would like to use that technology for their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html).
<div class="doc_info_bubble">
The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before decid the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consiider adding it and making a pull request!
The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before deciding to use the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consider adding it and making a pull request!
</div>
## Features
@ -27,7 +27,7 @@ An example server can be found in the Git repo [here](https://github.com/jamesag
The set-up of a JAX-RS server goes beyond the scope of this documentation. The implementation of the server follows the same pattern as the standard server. It is required to put the correct [annotation](./rest_operations.html) on the methods in the [Resource Providers](./resource_providers.html) in order to be able to call them.
Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The <code><a href="https://docs.oracle.com/javaee/6/api/javax/ws/rs/Produces.html">@Produces</a></code> annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitely in order to avoid problems with proxy classes in a Java EE environment.
Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The <code><a href="https://docs.oracle.com/javaee/6/api/javax/ws/rs/Produces.html">@Produces</a></code> annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitly in order to avoid problems with proxy classes in a Java EE environment.
It is necessary to extend the abstract class [AbstractJaxRsResourceProvide](/hapi-fhir/apidocs/hapi-fhir-jaxrsserver-base/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.html).

View File

@ -1,4 +1,4 @@
# Multitenency
# Multitenancy
If you wish to allow a single endpoint to support multiple tenants, you may supply the server with a multitenancy provider.

View File

@ -76,7 +76,7 @@ In some cases, it may be useful to have access to the underlying HttpServletRequ
# REST Exception/Error Handling
Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution you may also need to propagate errors back to the client for a variety of reasons.
Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution, you may also need to propagate errors back to the client for a variety of reasons.
## Automatic Exception Handling

View File

@ -8,7 +8,7 @@ to create your server) and the overlay drops a number of files into your project
# Adding the Overlay
These instructions assume that you have an exsiting web project which uses Maven to build. The POM.xml should have a "packaging" type of "war".
These instructions assume that you have an existing web project which uses Maven to build. The POM.xml should have a "packaging" type of "war".
Adding the overlay to your project is relatively simple. First, add the "hapi-fhir-testpage-overlay" dependency to the dependencies section of your POM.xml file.
@ -54,7 +54,7 @@ Then, add the following WAR plugin to the plugins section of your POM.xml
</build>
```
Then, create a Java source file called `FhirTesterConfig.java` and copy in the following contents:
Then, create a Java source file called `FhirTesterConfig.java` and copy the following contents:
<macro name="snippet">
<param name="file" value="restful-server-example/src/main/java/ca/uhn/example/config/FhirTesterConfig.java" />

View File

@ -38,6 +38,9 @@ java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
```
Individual commands can be troubleshooted by adding the `--debug` command line argument.
If this does not help, please post a question on our [Google Group](https://groups.google.com/d/forum/hapi-fhir).
# Server (run-server)

View File

@ -1,6 +1,6 @@
# Schema / Schematron Validator
FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complimentary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets.
FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complementary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets.
The two sets of files are included with HAPI, and it uses them to perform validation.
@ -11,7 +11,7 @@ The Schema/Schematron validators were recommended early in the development of FH
# Preparation
In order to use HAPI's Schematron support, a libaray called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.)
In order to use HAPI's Schematron support, a library called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.)
Note that this library is specified as an optional dependency by HAPI FHIR so you need to explicitly include it if you want to use this functionality.

View File

@ -40,7 +40,12 @@ import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;

View File

@ -20,14 +20,31 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@ -48,21 +65,34 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.persistence.criteria.*;
import java.util.*;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
@Component
@Scope("prototype")
@ -273,20 +303,41 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
} else {
try {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType());
resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass());
} catch (DataFormatException e) {
throw new InvalidRequestException("Invalid resource type: " + theReferenceParam.getResourceType());
throw newInvalidResourceTypeException(theReferenceParam.getResourceType());
}
}
// Handle chain on _type
if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) {
String typeValue = theReferenceParam.getValue();
Class<? extends IBaseResource> wantedType;
try {
wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass();
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(wantedType)) {
throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
}
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
myQueryRoot.addPredicate(targetTypeParameter);
return targetTypeParameter;
}
boolean foundChainMatch = false;
List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>();
for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
@ -936,4 +987,18 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return retVal;
}
@NotNull
private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
String searchParamName = theResourceName + ":" + theParamName;
String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
return new InvalidRequestException(msg);
}
@NotNull
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
throw new InvalidRequestException(msg);
}
}

View File

@ -42,7 +42,7 @@ public class TermConceptProperty implements Serializable {
private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
public static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))

View File

@ -316,6 +316,67 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, empty());
}
@Test
public void testChainOnType() {
Patient sub1 = new Patient();
sub1.setActive(true);
sub1.addIdentifier().setSystem("foo").setValue("bar");
String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue();
Group sub2 = new Group();
sub2.setActive(true);
sub2.addIdentifier().setSystem("foo").setValue("bar");
String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue();
Encounter enc1 = new Encounter();
enc1.getSubject().setReference(sub1Id);
String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue();
Encounter enc2 = new Encounter();
enc2.getSubject().setReference(sub2Id);
String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue();
List<String> ids;
SearchParameterMap map;
IBundleProvider results;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Patient").setChain("_type"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Group").setChain("_type"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc2Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Organization").setChain("_type"));
try {
myEncounterDao.search(map);
fail();
} catch (InvalidRequestException e) {
assertEquals("Resource type \"Organization\" is not a valid target type for reference search parameter: Encounter:subject", e.getMessage());
}
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "HelpImABug").setChain("_type"));
try {
myEncounterDao.search(map);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid/unsupported resource type: \"HelpImABug\"", e.getMessage());
}
}
/**
* See #441
*/

View File

@ -38,6 +38,7 @@ import java.util.Collections;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
@ -382,7 +383,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
@Test
public void testValidateResourceContainingProfileDeclarationInvalid() throws Exception {
public void testValidateResourceContainingProfileDeclarationInvalid() {
String methodName = "testValidateResourceContainingProfileDeclarationInvalid";
Observation input = new Observation();
@ -408,6 +409,42 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
}
@Test
public void testValidateBundleContainingResourceContainingProfileDeclarationInvalid() {
String methodName = "testValidateResourceContainingProfileDeclarationInvalid";
Observation observation = new Observation();
String profileUri = "http://example.com/StructureDefinition/" + methodName;
observation.getMeta().getProfile().add(new CanonicalType(profileUri));
observation.addIdentifier().setSystem("http://acme").setValue("12345");
observation.getEncounter().setReference("http://foo.com/Encounter/9");
observation.setStatus(ObservationStatus.FINAL);
observation.getCode().addCoding().setSystem("http://loinc.org").setCode("12345");
Bundle input = new Bundle();
input.setType(Bundle.BundleType.TRANSACTION);
input.addEntry()
.setResource(observation)
.setFullUrl("http://example.com/Observation")
.getRequest()
.setUrl("http://example.com/Observation")
.setMethod(Bundle.HTTPVerb.POST);
ValidationModeEnum mode = ValidationModeEnum.CREATE;
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
ourLog.info(encoded);
try {
myBundleDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
}
}
@Test
public void testValidateWithCanonicalReference() {
FhirInstanceValidator val = AopTestUtils.getTargetObject(myValidatorModule);

View File

@ -117,6 +117,23 @@ public class SearchParamExtractorR4Test {
}
@Test
public void testExtractSearchParamTokenTest() {
Patient p = new Patient();
p.addIdentifier().setSystem("sys").setValue("val");
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> params = extractor.extractSearchParamTokens(p, param);
assertEquals(1, params.size());
ResourceIndexedSearchParamToken paramValue = (ResourceIndexedSearchParamToken) params.iterator().next();
assertEquals("identifier", paramValue.getParamName());
assertEquals("sys", paramValue.getSystem());
assertEquals("val", paramValue.getValue());
}
@Test
public void testExtensionContainingReference() {
String path = "Patient.extension('http://patext').value.as(Reference)";

View File

@ -346,7 +346,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
.returnBundle(Bundle.class)
.execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid resource type: FOO", e.getMessage());
assertEquals("HTTP 400 Bad Request: Invalid/unsupported resource type: \"FOO\"", e.getMessage());
}
}

View File

@ -405,15 +405,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(idValues, contains(pid));
// Search param on extension
myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg=" + orgId.getValue());
myCaptureQueriesListener.logSelectQueries();
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.name=ORGANIZATION");
assertThat(idValues, contains(pid));
myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.name=PARENT");
myCaptureQueriesListener.logSelectQueries();
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT");

View File

@ -173,6 +173,11 @@ public abstract class BaseTask {
myFailureAllowed = theFailureAllowed;
}
protected boolean isFailureAllowed() {
return myFailureAllowed;
}
public String getFlywayVersion() {
String releasePart = myProductVersion;
if (releasePart.startsWith("V")) {

View File

@ -62,10 +62,17 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
}
Long taskColumnLength = getColumnLength();
if (taskColumnLength != null && isNoColumnShrink()) {
boolean isShrinkOnly = false;
if (taskColumnLength != null) {
long existingLength = existingType.getLength() != null ? existingType.getLength() : 0;
if (existingLength > taskColumnLength) {
taskColumnLength = existingLength;
if (isNoColumnShrink()) {
taskColumnLength = existingLength;
} else {
if (existingType.getColumnTypeEnum() == getColumnType()) {
isShrinkOnly = true;
}
}
}
}
@ -129,6 +136,10 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
throw new IllegalStateException("Dont know how to handle " + getDriverType());
}
if (!isFailureAllowed() && isShrinkOnly) {
setFailureAllowed(true);
}
logInfo(ourLog, "Updating column {} on table {} to type {}", getColumnName(), getTableName(), type);
if (sql != null) {
executeSql(getTableName(), sql);
@ -139,4 +150,5 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
executeSql(getTableName(), sqlNotNull);
}
}
}

View File

@ -269,4 +269,29 @@ public class ModifyColumnTest extends BaseTest {
assertTrue(existingColumnType.equals(task.getColumnType(), task.getColumnLength()));
}
@Test
public void testShrinkDoesntFailIfShrinkCannotProceed() throws SQLException {
executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(10))");
executeSql("insert into SOMETABLE (PID, TEXTCOL) values (1, '0123456789')");
ModifyColumnTask task = new ModifyColumnTask("1", "123456.7");
task.setTableName("SOMETABLE");
task.setColumnName("TEXTCOL");
task.setColumnType(AddColumnTask.ColumnTypeEnum.STRING);
task.setNullable(true);
task.setColumnLength(5);
getMigrator().addTask(task);
getMigrator().migrate();
assertEquals(1, task.getExecutedStatements().size());
assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL"));
// Make sure additional migrations don't crash
getMigrator().migrate();
getMigrator().migrate();
}
}

View File

@ -186,7 +186,19 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
}
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>();
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate);
return setToPopulate;
}
private IExtractor<BaseResourceIndexedSearchParam> createTokenExtractor(IBaseResource theResource) {
String resourceTypeName = toRootTypeName(theResource);
String useSystem;
if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) {
@ -204,85 +216,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
IExtractor<BaseResourceIndexedSearchParam> extractor = (params, searchParam, value, path) -> {
// DSTU3+
if (value instanceof IBaseEnumeration<?>) {
IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value;
String system = extractSystem(obj);
String code = obj.getValueAsString();
createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code);
return;
}
// DSTU2 only
if (value instanceof BoundCodeDt) {
BoundCodeDt boundCode = (BoundCodeDt) value;
Enum valueAsEnum = boundCode.getValueAsEnum();
String system = null;
if (valueAsEnum != null) {
//noinspection unchecked
system = boundCode.getBinder().toSystemString(valueAsEnum);
}
String code = boundCode.getValueAsString();
createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code);
return;
}
if (value instanceof IPrimitiveType) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value;
String systemAsString = null;
String valueAsString = nextValue.getValueAsString();
if ("CodeSystem.concept.code".equals(path)) {
systemAsString = useSystem;
} else if ("ValueSet.codeSystem.concept.code".equals(path)) {
systemAsString = useSystem;
}
createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, systemAsString, valueAsString);
return;
}
switch (path) {
case "Patient.communication":
addToken_PatientCommunication(resourceTypeName, params, searchParam, value);
return;
case "Consent.source":
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
return;
case "Location.position":
addCoords_Position(resourceTypeName, params, searchParam, value);
return;
case "StructureDefinition.context":
// TODO: implement this
ourLog.warn("StructureDefinition context indexing not currently supported");
return;
case "CapabilityStatement.rest.security":
addToken_CapabilityStatementRestSecurity(resourceTypeName, params, searchParam, value);
return;
}
String nextType = toRootTypeName(value);
switch (nextType) {
case "Identifier":
addToken_Identifier(resourceTypeName, params, searchParam, value);
break;
case "CodeableConcept":
addToken_CodeableConcept(resourceTypeName, params, searchParam, value);
break;
case "Coding":
addToken_Coding(resourceTypeName, params, searchParam, value);
break;
case "ContactPoint":
addToken_ContactPoint(resourceTypeName, params, searchParam, value);
break;
default:
addUnexpectedDatatypeWarning(params, searchParam, value);
break;
}
};
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
return new TokenExtractor(resourceTypeName, useSystem);
}
@Override
@ -798,25 +732,29 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
continue;
}
String nextPathUnsplit = nextSpDef.getPath();
if (isBlank(nextPathUnsplit)) {
continue;
}
extractSearchParam(nextSpDef, theResource, theExtractor, retVal);
}
return retVal;
}
String[] splitPaths = split(nextPathUnsplit);
for (String nextPath : splitPaths) {
nextPath = trim(nextPath);
for (IBase nextObject : extractValues(nextPath, theResource)) {
if (nextObject != null) {
String typeName = toRootTypeName(nextObject);
if (!myIgnoredForSearchDatatypes.contains(typeName)) {
theExtractor.extract(retVal, nextSpDef, nextObject, nextPath);
}
private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate) {
String nextPathUnsplit = theSearchParameterDef.getPath();
if (isBlank(nextPathUnsplit)) {
return;
}
String[] splitPaths = split(nextPathUnsplit);
for (String nextPath : splitPaths) {
nextPath = trim(nextPath);
for (IBase nextObject : extractValues(nextPath, theResource)) {
if (nextObject != null) {
String typeName = toRootTypeName(nextObject);
if (!myIgnoredForSearchDatatypes.contains(typeName)) {
theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath);
}
}
}
}
return retVal;
}
private String toRootTypeName(IBase nextObject) {
@ -1032,6 +970,95 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
private class TokenExtractor implements IExtractor<BaseResourceIndexedSearchParam> {
private final String myResourceTypeName;
private final String myUseSystem;
public TokenExtractor(String theResourceTypeName, String theUseSystem) {
myResourceTypeName = theResourceTypeName;
myUseSystem = theUseSystem;
}
@Override
public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path) {
// DSTU3+
if (value instanceof IBaseEnumeration<?>) {
IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value;
String system = extractSystem(obj);
String code = obj.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code);
return;
}
// DSTU2 only
if (value instanceof BoundCodeDt) {
BoundCodeDt boundCode = (BoundCodeDt) value;
Enum valueAsEnum = boundCode.getValueAsEnum();
String system = null;
if (valueAsEnum != null) {
//noinspection unchecked
system = boundCode.getBinder().toSystemString(valueAsEnum);
}
String code = boundCode.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code);
return;
}
if (value instanceof IPrimitiveType) {
IPrimitiveType<?> nextValue = (IPrimitiveType<?>) value;
String systemAsString = null;
String valueAsString = nextValue.getValueAsString();
if ("CodeSystem.concept.code".equals(path)) {
systemAsString = myUseSystem;
} else if ("ValueSet.codeSystem.concept.code".equals(path)) {
systemAsString = myUseSystem;
}
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, systemAsString, valueAsString);
return;
}
switch (path) {
case "Patient.communication":
BaseSearchParamExtractor.this.addToken_PatientCommunication(myResourceTypeName, params, searchParam, value);
return;
case "Consent.source":
// Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
return;
case "Location.position":
BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value);
return;
case "StructureDefinition.context":
// TODO: implement this
ourLog.warn("StructureDefinition context indexing not currently supported");
return;
case "CapabilityStatement.rest.security":
BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(myResourceTypeName, params, searchParam, value);
return;
}
String nextType = BaseSearchParamExtractor.this.toRootTypeName(value);
switch (nextType) {
case "Identifier":
BaseSearchParamExtractor.this.addToken_Identifier(myResourceTypeName, params, searchParam, value);
break;
case "CodeableConcept":
BaseSearchParamExtractor.this.addToken_CodeableConcept(myResourceTypeName, params, searchParam, value);
break;
case "Coding":
BaseSearchParamExtractor.this.addToken_Coding(myResourceTypeName, params, searchParam, value);
break;
case "ContactPoint":
BaseSearchParamExtractor.this.addToken_ContactPoint(myResourceTypeName, params, searchParam, value);
break;
default:
BaseSearchParamExtractor.this.addUnexpectedDatatypeWarning(params, searchParam, value);
break;
}
}
}
private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) {
BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -42,6 +43,8 @@ public interface ISearchParamExtractor {
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource);
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource);

View File

@ -31,13 +31,20 @@ import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public abstract class SubscriptionDeliveryHandlerFactory {
public class SubscriptionDeliveryHandlerFactory {
private IEmailSender myEmailSender;
@Lookup
protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender);
protected SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender) {
// stub method since this is a @Lookup
throw new IllegalStateException();
}
@Lookup
protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber();
protected SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber() {
// stub method since this is a @Lookup
throw new IllegalStateException();
}
public Optional<MessageHandler> createDeliveryHandler(CanonicalSubscriptionChannelType theChannelType) {
if (theChannelType == CanonicalSubscriptionChannelType.EMAIL) {

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.TestDstu2Config;
import ca.uhn.fhirtest.config.TestDstu3Config;
@ -198,6 +199,11 @@ public class TestRestfulServer extends RestfulServer {
CorsInterceptor corsInterceptor = new CorsInterceptor();
registerInterceptor(corsInterceptor);
/*
* Enable FHIRPath evaluation
*/
registerInterceptor(new FhirPathFilterInterceptor());
/*
* Enable version conversion
*/

View File

@ -462,6 +462,15 @@ public abstract class RequestDetails {
if (myRequestContents == null) {
myRequestContents = getByteStreamRequestContents();
}
return getRequestContentsIfLoaded();
}
/**
* Returns the request contents if they were loaded, returns <code>null</code> otherwise
*
* @see #loadRequestContents()
*/
public byte[] getRequestContentsIfLoaded() {
return myRequestContents;
}

View File

@ -22,6 +22,8 @@ package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@ -42,9 +44,11 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.ElementsParameter;
import ca.uhn.fhir.rest.server.method.SummaryEnumParameter;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.DateUtils;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.Sets;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseReference;
@ -73,7 +77,7 @@ public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
private static final HashSet<String> TEXT_ENCODE_ELEMENTS = new HashSet<>(Arrays.asList("*.text", "*.id", "*.meta", "*.(mandatory)"));
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<FhirVersionEnum, FhirContext>());
private static Map<FhirVersionEnum, FhirContext> myFhirContextMap = Collections.synchronizedMap(new HashMap<>());
private enum NarrativeModeEnum {
NORMAL, ONLY, SUPPRESS;
@ -163,7 +167,7 @@ public class RestfulServerUtils {
}
if (summaryModeCount) {
parser.setEncodeElements(Collections.singleton("Bundle.total"));
parser.setEncodeElements(Sets.newHashSet("Bundle.total", "Bundle.type"));
} else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
@ -892,6 +896,19 @@ public class RestfulServerUtils {
String charset = Constants.CHARSET_NAME_UTF8;
Writer writer = response.getResponseWriter(theStatusCode, theStatusMessage, contentType, charset, respondGzip);
// Interceptor call: SERVER_OUTGOING_WRITER_CREATED
if (theServer.getInterceptorService() != null && theServer.getInterceptorService().hasHooks(Pointcut.SERVER_OUTGOING_WRITER_CREATED)) {
HookParams params = new HookParams()
.add(Writer.class, writer)
.add(RequestDetails.class, theRequestDetails)
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
Object newWriter = theServer.getInterceptorService().callHooksAndReturnObject(Pointcut.SERVER_OUTGOING_WRITER_CREATED, params);
if (newWriter != null) {
writer = (Writer) newWriter;
}
}
if (theResource == null) {
// No response is being returned
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {

View File

@ -0,0 +1,90 @@
package ca.uhn.fhir.rest.server.interceptor;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2020 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 ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This interceptor looks for a URL parameter on requests called <code>_fhirpath</code> and
* replaces the resource being returned with a Parameters resource containing the results of
* the given FHIRPath expression evaluated against the resource that would otherwise
* have been returned.
*
* @see <a href="https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#response-customizing-evaluate-fhirpath">Interceptors - Response Customization: Evaluate FHIRPath</a>
* @since 5.0.0
*/
public class FhirPathFilterInterceptor {
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public void preProcessOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails) {
IBaseResource responseResource = theResponseDetails.getResponseResource();
if (responseResource != null) {
String[] fhirPathParams = theRequestDetails.getParameters().get(Constants.PARAM_FHIRPATH);
if (fhirPathParams != null) {
FhirContext ctx = theRequestDetails.getFhirContext();
IBaseParameters responseParameters = ParametersUtil.newInstance(ctx);
for (String expression : fhirPathParams) {
if (isNotBlank(expression)) {
IBase resultPart = ParametersUtil.addParameterToParameters(ctx, responseParameters, "result");
ParametersUtil.addPartString(ctx, resultPart, "expression", expression);
IFhirPath fhirPath = ctx.newFhirPath();
List<IBase> outputs;
try {
outputs = fhirPath.evaluate(responseResource, expression, IBase.class);
} catch (FhirPathExecutionException e) {
throw new InvalidRequestException("Error parsing FHIRPath expression: " + e.getMessage());
}
for (IBase nextOutput : outputs) {
if (nextOutput instanceof IBaseResource) {
ParametersUtil.addPartResource(ctx, resultPart, "result", (IBaseResource) nextOutput);
} else {
ParametersUtil.addPart(ctx, resultPart, "result", nextOutput);
}
}
}
}
theResponseDetails.setResponseResource(responseParameters);
}
}
}
}

View File

@ -23,10 +23,12 @@ package ca.uhn.fhir.rest.server.interceptor;
public class InterceptorOrders {
public static final int SERVE_MEDIA_RESOURCE_RAW_INTERCEPTOR = 1000;
public static final int RESPONSE_HIGHLIGHTER_INTERCEPTOR = 10000;
public static final int RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED = -1;
/** Non instantiable */
/**
* Non instantiable
*/
private InterceptorOrders() {
// nothing
}

View File

@ -0,0 +1,123 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.apache.commons.lang3.Validate;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* This interceptor captures and makes
* available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response
* stream for FHIR responses.
* <p>
* Response details are made available in the request {@link RequestDetails#getUserData() RequestDetails UserData map}
* with {@link #RESPONSE_RESULT_KEY} as the key.
* </p>
*
* @since 5.0.0
*/
public class ResponseSizeCapturingInterceptor {
/**
* If the response was a character stream, a character count will be placed in the
* {@link RequestDetails#getUserData() RequestDetails UserData map} with this key, containing
* an {@link Result} value.
* <p>
* The value will be placed at the start of the {@link Pointcut#SERVER_PROCESSING_COMPLETED} pointcut, so it will not
* be available before that time.
* </p>
*/
public static final String RESPONSE_RESULT_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_RESPONSE_RESULT_KEY";
private static final String COUNTING_WRITER_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_COUNTING_WRITER_KEY";
private List<Consumer<Result>> myConsumers = new ArrayList<>();
@Hook(Pointcut.SERVER_OUTGOING_WRITER_CREATED)
public Writer capture(RequestDetails theRequestDetails, Writer theWriter) {
CountingWriter retVal = new CountingWriter(theWriter);
theRequestDetails.getUserData().put(COUNTING_WRITER_KEY, retVal);
return retVal;
}
@Hook(value = Pointcut.SERVER_PROCESSING_COMPLETED, order = InterceptorOrders.RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED)
public void completed(RequestDetails theRequestDetails) {
CountingWriter countingWriter = (CountingWriter) theRequestDetails.getUserData().get(COUNTING_WRITER_KEY);
if (countingWriter != null) {
int charCount = countingWriter.getCount();
Result result = new Result(charCount);
notifyConsumers(result);
theRequestDetails.getUserData().put(RESPONSE_RESULT_KEY, result);
}
}
/**
* Registers a new consumer. All consumers will be notified each time a request is complete.
*
* @param theConsumer The consumer
*/
public void registerConsumer(@Nonnull Consumer<Result> theConsumer) {
Validate.notNull(theConsumer);
myConsumers.add(theConsumer);
}
private void notifyConsumers(Result theResult) {
myConsumers.forEach(t -> t.accept(theResult));
}
/**
* Contains the results of the capture
*/
public static class Result {
private final int myWrittenChars;
public Result(int theWrittenChars) {
myWrittenChars = theWrittenChars;
}
public int getWrittenChars() {
return myWrittenChars;
}
}
private static class CountingWriter extends Writer {
private final Writer myWrap;
private int myCount;
private CountingWriter(Writer theWrap) {
myWrap = theWrap;
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
myCount += len;
myWrap.write(cbuf, off, len);
}
@Override
public void flush() throws IOException {
myWrap.flush();
}
@Override
public void close() throws IOException {
myWrap.close();
}
public int getCount() {
return myCount;
}
}
}

View File

@ -60,7 +60,7 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimeResourceBlockDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFluentPath;
import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;

View File

@ -74,6 +74,16 @@
<artifactId>commons-codec</artifactId>
</dependency>
<!--
Gson is needed for some utility classes in org.hl7.fhir.dstu2016may, so
this is necessary for the JavaDocs to build
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<!--
<dependency>
<groupId>net.sf.saxon</groupId>

View File

@ -30,7 +30,7 @@ import org.hl7.fhir.dstu2016may.model.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@ -41,7 +41,7 @@ public class FhirDstu2_1 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}

View File

@ -23,11 +23,11 @@ package ca.uhn.fhir.model.dstu2;
import java.io.InputStream;
import java.util.Date;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.*;
import ca.uhn.fhir.model.dstu2.composite.*;
@ -42,7 +42,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}

View File

@ -165,6 +165,16 @@
<version>1.1</version>
</dependency>
<!--
Gson is needed for some utility classes in org.hl7.fhir.dstu3, so
this is necessary for the JavaDocs to build
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3</artifactId>

View File

@ -24,13 +24,13 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
import ca.uhn.fhir.util.ReflectionUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.hapi.fluentpath.FluentPathDstu3;
import org.hl7.fhir.dstu3.hapi.fluentpath.FhirPathDstu3;
import org.hl7.fhir.dstu3.hapi.rest.server.Dstu3BundleFactory;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.*;
@ -44,8 +44,8 @@ public class FhirDstu3 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
return new FluentPathDstu3(theFhirContext);
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathDstu3(theFhirContext);
}
@Override

View File

@ -2,8 +2,8 @@ package org.hl7.fhir.dstu3.hapi.fluentpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
@ -13,11 +13,11 @@ import org.hl7.fhir.instance.model.api.IBase;
import java.util.List;
import java.util.Optional;
public class FluentPathDstu3 implements IFluentPath {
public class FhirPathDstu3 implements IFhirPath {
private FHIRPathEngine myEngine;
public FluentPathDstu3(FhirContext theCtx) {
public FhirPathDstu3(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport));
}
@ -29,12 +29,12 @@ public class FluentPathDstu3 implements IFluentPath {
try {
result = myEngine.evaluate((Base)theInput, thePath);
} catch (FHIRException e) {
throw new FluentPathExecutionException(e);
throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}

View File

@ -85,6 +85,16 @@
<optional>true</optional>
</dependency>
<!--
Gson is needed for some utility classes in org.hl7.fhir.dstu2, so
this is necessary for the JavaDocs to build
-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.xmlunit</groupId>

View File

@ -24,12 +24,12 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu2.model.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@ -41,7 +41,7 @@ public class FhirDstu2Hl7Org implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}

View File

@ -116,6 +116,11 @@
<version>1.4</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
@ -219,11 +224,6 @@
</dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>

View File

@ -26,12 +26,12 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.hapi.fluentpath.FluentPathR4;
import org.hl7.fhir.r4.hapi.fluentpath.FhirPathR4;
import org.hl7.fhir.r4.hapi.rest.server.R4BundleFactory;
import org.hl7.fhir.r4.model.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@ -42,8 +42,8 @@ public class FhirR4 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
return new FluentPathR4(theFhirContext);
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathR4(theFhirContext);
}
@Override

View File

@ -2,8 +2,8 @@ package org.hl7.fhir.r4.hapi.fluentpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
@ -13,11 +13,11 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine;
import java.util.List;
import java.util.Optional;
public class FluentPathR4 implements IFluentPath {
public class FhirPathR4 implements IFhirPath {
private FHIRPathEngine myEngine;
public FluentPathR4(FhirContext theCtx) {
public FhirPathR4(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport));
}
@ -29,12 +29,12 @@ public class FluentPathR4 implements IFluentPath {
try {
result = myEngine.evaluate((Base) theInput, thePath);
} catch (FHIRException e) {
throw new FluentPathExecutionException(e);
throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}

View File

@ -5,7 +5,10 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
import com.google.common.io.Resources;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.lang.StringUtils;
@ -18,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@ -150,6 +154,23 @@ public class JsonParserR4Test extends BaseTest {
}
@Test
public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
URL url = Resources.getResource("bundle-with-two-patient-resources.json");
String text = Resources.toString(url, Charsets.UTF_8);
Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text);
assertEquals("12346", getPatientIdValue(bundle, 0));
assertEquals("12345", getPatientIdValue(bundle, 1));
}
private String getPatientIdValue(Bundle input, int entry) {
final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource();
final Patient patient = (Patient) documentReference.getSubject().getResource();
return patient.getIdentifier().get(0).getValue();
}
/**
* See #814
*/
@ -768,6 +789,40 @@ public class JsonParserR4Test extends BaseTest {
return b;
}
/**
* Ensure that a contained bundle doesn't cause a crash
*/
@Test
public void testEncodeContainedBundle() {
String auditEvent = "{\n" +
" \"resourceType\": \"AuditEvent\",\n" +
" \"contained\": [ {\n" +
" \"resourceType\": \"Bundle\",\n" +
" \"id\": \"REASONS\",\n" +
" \"entry\": [ {\n" +
" \"resource\": {\n" +
" \"resourceType\": \"Condition\",\n" +
" \"id\": \"123\"\n" +
" }\n" +
" } ]\n" +
" }, {\n" +
" \"resourceType\": \"MeasureReport\",\n" +
" \"id\": \"MRPT5000602611RD\",\n" +
" \"evaluatedResource\": [ {\n" +
" \"reference\": \"#REASONS\"\n" +
" } ]\n" +
" } ],\n" +
" \"entity\": [ {\n" +
" \"what\": {\n" +
" \"reference\": \"#MRPT5000602611RD\"\n" +
" }\n" +
" } ]\n" +
"}";
AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent);
String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae);
assertEquals(auditEvent, auditEventAsString);
}
@AfterClass
public static void afterClassClearContext() {

View File

@ -7,18 +7,26 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import ca.uhn.fhir.test.BaseTest;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.AuditEvent;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.context.FhirContext;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import java.io.IOException;
import java.net.URL;
public class XmlParserR4Test extends BaseTest {
private static final Logger ourLog = LoggerFactory.getLogger(XmlParserR4Test.class);
@ -79,6 +87,23 @@ public class XmlParserR4Test extends BaseTest {
}
@Test
public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
URL url = Resources.getResource("bundle-with-two-patient-resources.xml");
String text = Resources.toString(url, Charsets.UTF_8);
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, text);
assertEquals("12346", getPatientIdValue(bundle, 0));
assertEquals("12345", getPatientIdValue(bundle, 1));
}
private String getPatientIdValue(Bundle input, int entry) {
final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource();
final Patient patient = (Patient) documentReference.getSubject().getResource();
return patient.getIdentifier().get(0).getValue();
}
/**
* See #1658
*/
@ -91,5 +116,43 @@ public class XmlParserR4Test extends BaseTest {
ourLog.info(encoded);
}
/**
* Ensure that a contained bundle doesn't cause a crash
*/
@Test
public void testEncodeContainedBundle() {
String auditEvent = "<AuditEvent xmlns=\"http://hl7.org/fhir\">\n" +
" <contained>\n" +
" <Bundle xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"REASONS\"/>\n" +
" <entry>\n" +
" <resource>\n" +
" <Condition xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"123\"/>\n" +
" </Condition>\n" +
" </resource>\n" +
" </entry>\n" +
" </Bundle>\n" +
" </contained>\n" +
" <contained>\n" +
" <MeasureReport xmlns=\"http://hl7.org/fhir\">\n" +
" <id value=\"MRPT5000602611RD\"/>\n" +
" <evaluatedResource>\n" +
" <reference value=\"#REASONS\"/>\n" +
" </evaluatedResource>\n" +
" </MeasureReport>\n" +
" </contained>\n" +
" <entity>\n" +
" <what>\n" +
" <reference value=\"#MRPT5000602611RD\"/>\n" +
" </what>\n" +
" </entity>\n" +
"</AuditEvent>";
AuditEvent ae = ourCtx.newXmlParser().parseResource(AuditEvent.class, auditEvent);
String auditEventAsString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ae);
assertEquals(auditEvent, auditEventAsString);
}
}

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SearchStyleEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
@ -21,7 +22,6 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
@ -66,7 +66,16 @@ public class SearchR4Test {
ourIdentifiers = null;
}
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
private Bundle executeSearchAndValidateHasLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
Bundle bundle = executeSearch(httpGet, theExpectEncoding);
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
assertEquals(10, bundle.getEntry().size());
return bundle;
}
private Bundle executeSearch(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
Bundle bundle;
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
@ -76,9 +85,6 @@ public class SearchR4Test {
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
validate(bundle);
assertEquals(10, bundle.getEntry().size());
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertNotNull(linkNext);
}
return bundle;
}
@ -98,6 +104,21 @@ public class SearchR4Test {
}
/**
* See #1763
*/
@Test
public void testSummaryCount() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&"+Constants.PARAM_SUMMARY + "=" + SummaryEnum.COUNT.getCode());
Bundle bundle = executeSearch(httpGet, EncodingEnum.JSON);
ourLog.info(toJson(bundle));
assertEquals(200, bundle.getTotal());
assertEquals("searchset", bundle.getType().toCode());
assertEquals(0, bundle.getEntry().size());
}
@Test
public void testPagingPreservesElements() throws Exception {
HttpGet httpGet;
@ -107,7 +128,7 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
assertThat(linkSelf, containsString("_elements=name"));
@ -118,7 +139,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@ -126,7 +147,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@ -134,7 +155,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@ -203,25 +224,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
@ -235,26 +256,26 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), containsString("active"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
@ -268,25 +289,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
@ -301,28 +322,28 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
@ -336,25 +357,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));

View File

@ -0,0 +1,170 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.test.utilities.HttpClientRule;
import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule;
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class FhirPathFilterInterceptorTest {
private static final Logger ourLog = LoggerFactory.getLogger(FhirPathFilterInterceptorTest.class);
@ClassRule
public static HttpClientRule ourClientRule = new HttpClientRule();
private static FhirContext ourCtx = FhirContext.forR4();
@ClassRule
public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
@ClassRule
public static HashMapResourceProviderRule<Patient> ourProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class);
private IGenericClient myClient;
private String myBaseUrl;
private CloseableHttpClient myHttpClient;
private IIdType myPatientId;
@Before
public void before() {
ourProviderRule.clear();
ourServerRule.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
ourServerRule.getRestfulServer().getInterceptorService().registerInterceptor(new FhirPathFilterInterceptor());
myClient = ourServerRule.getFhirClient();
myBaseUrl = "http://localhost:" + ourServerRule.getPort();
myHttpClient = ourClientRule.getClient();
}
@Test
public void testUnfilteredResponse() throws IOException {
createPatient();
HttpGet request = new HttpGet(myPatientId.getValue());
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]"));
}
}
@Test
public void testUnfilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
createPatient();
HttpGet request = new HttpGet(myPatientId.getValue() + "?_format=" + Constants.FORMATS_HTML_JSON);
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("<span class='hlTagName'>&quot;system&quot;</span>: <span class='hlQuot'>&quot;http://identifiers/1&quot;"));
assertThat(responseText, containsString("<span class='hlTagName'>&quot;given&quot;</span>: <span class='hlControl'>[</span> <span class='hlTagName'>&quot;Homer&quot;</span><span class='hlControl'>,</span> <span class='hlTagName'>&quot;Jay&quot;</span> ]</div>"));
}
}
@Test
public void testFilteredResponse() throws IOException {
createPatient();
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_pretty=true");
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
assertThat(responseText, not(containsString("\"given\": [ \"Homer\", \"Jay\" ]")));
}
}
@Test
public void testFilteredResponse_ExpressionReturnsResource() throws IOException {
createPatient();
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient&_pretty=true");
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("\"resource\": {"));
assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]"));
}
}
@Test
public void testFilteredResponse_ExpressionIsInvalid() throws IOException {
createPatient();
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=" + UrlUtil.escapeUrlParam("***"));
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertEquals(400, response.getStatusLine().getStatusCode());
assertThat(responseText, containsString("Error parsing FHIRPath expression: Error performing *: left operand has more than one value"));
}
}
@Test
public void testFilteredResponseBundle() throws IOException {
createPatient();
HttpGet request = new HttpGet(myBaseUrl + "/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true");
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString(
" \"valueHumanName\": {\n" +
" \"family\": \"Simpson\",\n" +
" \"given\": [ \"Homer\", \"Jay\" ]\n" +
" }"
));
}
}
@Test
public void testFilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
createPatient();
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_format=" + Constants.FORMATS_HTML_JSON);
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("<span class='hlTagName'>&quot;system&quot;</span>: <span class='hlQuot'>&quot;http://identifiers/1&quot;"));
assertThat(responseText, not(containsString("<span class='hlTagName'>&quot;given&quot;</span>: <span class='hlControl'>[</span> <span class='hlTagName'>&quot;Homer&quot;</span><span class='hlControl'>,</span> <span class='hlTagName'>&quot;Jay&quot;</span> ]</div>")));
}
}
private void createPatient() {
Patient p = new Patient();
p.setActive(true);
p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1");
p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2");
p.addName().setFamily("Simpson").addGiven("Homer").addGiven("Jay");
p.addName().setFamily("Simpson").addGiven("Grandpa");
myPatientId = myClient.create().resource(p).execute().getId().withServerBase(myBaseUrl, "Patient");
}
}

View File

@ -0,0 +1,70 @@
package ca.uhn.fhir.rest.server.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule;
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.function.Consumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class ResponseSizeCapturingInterceptorTest {
private static FhirContext ourCtx = FhirContext.forR4();
@ClassRule
public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
private ResponseSizeCapturingInterceptor myInterceptor;
@Rule
public HashMapResourceProviderRule<Patient> myPatientProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class);
@Mock
private Consumer<ResponseSizeCapturingInterceptor.Result> myConsumer;
@Captor
private ArgumentCaptor<ResponseSizeCapturingInterceptor.Result> myResultCaptor;
@Before
public void before() {
myInterceptor = new ResponseSizeCapturingInterceptor();
ourServerRule.getRestfulServer().registerInterceptor(myInterceptor);
}
@After
public void after() {
ourServerRule.getRestfulServer().unregisterInterceptor(myInterceptor);
}
@Test
public void testReadResource() {
Patient resource = new Patient();
resource.setActive(true);
IIdType id = ourServerRule.getFhirClient().create().resource(resource).execute().getId().toUnqualifiedVersionless();
myInterceptor.registerConsumer(myConsumer);
resource = ourServerRule.getFhirClient().read().resource(Patient.class).withId(id).execute();
assertEquals(true, resource.getActive());
verify(myConsumer, times(1)).accept(myResultCaptor.capture());
assertEquals(100, myResultCaptor.getValue().getWrittenChars());
}
}

View File

@ -0,0 +1,260 @@
{
"resourceType": "Bundle",
"meta": {
"profile": [
"http://forcare.com/fhir/createCda"
]
},
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "DocumentReference",
"id": "doc1",
"contained": [
{
"resourceType": "Patient",
"id": "patient",
"identifier": [
{
"system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7",
"value": "12346"
}
],
"name": [
{
"use": "official",
"family": "Beugels",
"given": [
"Kees"
]
}
],
"gender": "male",
"birthDate": "1970-01-01"
}
],
"status": "current",
"type": {
"coding": [
{
"system": "2.16.840.1.113883.6.1",
"code": "57016-8",
"display": "Privacy Policy Acknowledgment"
}
]
},
"category": [
{
"coding": [
{
"system": "2.16.840.1.113883.6.1",
"code": "57016-8",
"display": "Privacy Policy Acknowledgment"
}
]
}
],
"subject": {
"reference": "#patient"
},
"date": "2016-05-04T08:18:03.203Z",
"author": [
{
"reference": "#patient"
}
],
"description": "Hospital Privacy Consent",
"securityLabel": [
{
"coding": [
{
"system": "2.16.840.1.113883.5.25",
"code": "N",
"display": "Normal"
}
]
}
],
"content": [
{
"attachment": {
"contentType": "text/xml",
"language": "en-US",
"url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5"
},
"format": {
"system": "1.3.6.1.4.1.19376.1.2.3",
"code": "urn:ihe:iti:bppc-sd:2007",
"display": "Basic Patient Privacy Consent (scanned part)"
}
}
],
"context": {
"event": [
{
"coding": [
{
"system": "1.2.826.0.1.3680043.2.1611.2.10",
"code": "allowMedicalDoctorsFromHospitalAToSeeDocuments",
"display": "I allow Medical Doctors in Hospital A to access my medical record"
}
]
},
{
"coding": [
{
"system": "1.2.826.0.1.3680043.2.1611.2.10",
"code": "denyMedicalDoctorsFromHospitalBToSeeDocuments",
"display": "I deny Medical Doctors in Hospital B to access my medical record"
}
]
}
],
"period": {
"start": "2016-05-04T08:18:03.203Z",
"end": "2016-09-04T08:18:03.203Z"
},
"facilityType": {
"coding": [
{
"system": "2.16.840.1.113883.5.10588",
"code": "HOSP",
"display": "Hospital"
}
]
},
"practiceSetting": {
"coding": [
{
"system": "2.16.840.1.113883.2.1.6.8",
"code": "300",
"display": "General Medicine"
}
]
}
}
}
},
{
"resource": {
"resourceType": "DocumentReference",
"id": "doc2",
"contained": [
{
"resourceType": "Patient",
"id": "patient",
"identifier": [
{
"system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7",
"value": "12345"
}
],
"name": [
{
"use": "official",
"family": "Baker",
"given": [
"Rob"
]
}
],
"gender": "male",
"birthDate": "1970-01-01"
}
],
"status": "current",
"type": {
"coding": [
{
"system": "2.16.840.1.113883.6.1",
"code": "57016-8",
"display": "Privacy Policy Acknowledgment"
}
]
},
"category": [
{
"coding": [
{
"system": "2.16.840.1.113883.6.1",
"code": "57016-8",
"display": "Privacy Policy Acknowledgment"
}
]
}
],
"subject": {
"reference": "#patient"
},
"date": "2016-05-04T08:18:03.203Z",
"author": [
{
"reference": "#patient"
}
],
"description": "GPs Privacy Consent",
"securityLabel": [
{
"coding": [
{
"system": "2.16.840.1.113883.5.25",
"code": "N",
"display": "Normal"
}
]
}
],
"content": [
{
"attachment": {
"contentType": "text/xml",
"language": "en-US",
"url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5"
},
"format": {
"system": "1.3.6.1.4.1.19376.1.2.3",
"code": "urn:ihe:iti:bppc-sd:2007",
"display": "Basic Patient Privacy Consent (scanned part)"
}
}
],
"context": {
"event": [
{
"coding": [
{
"system": "1.2.826.0.1.3680043.2.1611.2.10",
"code": "denyGeneralPractitionersFromHestiaToSeeDocuments",
"display": "I deny Medical Doctors in Hestia General Practitioners to access my medical record"
}
]
}
],
"period": {
"start": "2016-05-04T08:18:03.203Z",
"end": "2016-09-04T08:18:03.203Z"
},
"facilityType": {
"coding": [
{
"system": "2.16.840.1.113883.5.10588",
"code": "HOSP",
"display": "Hospital"
}
]
},
"practiceSetting": {
"coding": [
{
"system": "2.16.840.1.113883.2.1.6.8",
"code": "300",
"display": "General Medicine"
}
]
}
}
}
}
]
}

View File

@ -0,0 +1,197 @@
<Bundle xmlns="http://hl7.org/fhir">
<meta>
<profile value="http://forcare.com/fhir/createCda" />
</meta>
<type value="transaction" />
<entry>
<resource>
<DocumentReference>
<id value="doc1" />
<contained>
<Patient>
<id value="patient" />
<identifier>
<system value="urn:oid:1.3.6.1.4.1.21367.2005.3.7" />
<value value="12346" />
</identifier>
<name>
<use value="official" />
<family value="Beugels" />
<given value="Kees" />
</name>
<gender value="male" />
<birthDate value="1970-01-01" />
</Patient>
</contained>
<subject>
<reference value="#patient" />
</subject>
<type>
<coding>
<system value="2.16.840.1.113883.6.1" />
<code value="57016-8" />
<display value="Privacy Policy Acknowledgment" />
</coding>
</type>
<category>
<coding>
<system value="2.16.840.1.113883.6.1" />
<code value="57016-8" />
<display value="Privacy Policy Acknowledgment" />
</coding>
</category>
<author>
<reference value="#patient" />
</author>
<date value="2016-05-04T08:18:03.203Z" />
<status value="current" />
<description value="Hospital Privacy Consent" />
<securityLabel>
<coding>
<system value="2.16.840.1.113883.5.25" />
<code value="N" />
<display value="Normal" />
</coding>
</securityLabel>
<content>
<attachment>
<contentType value="text/xml" />
<language value="en-US" />
<url value="urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5" />
</attachment>
<format>
<system value="1.3.6.1.4.1.19376.1.2.3" />
<code value="urn:ihe:iti:bppc-sd:2007" />
<display value="Basic Patient Privacy Consent (scanned part)" />
</format>
</content>
<context>
<event>
<coding>
<system value="1.2.826.0.1.3680043.2.1611.2.10" />
<code value="allowMedicalDoctorsFromHospitalAToSeeDocuments" />
<display value="I allow Medical Doctors in Hospital A to access my medical record" />
</coding>
</event>
<event>
<coding>
<system value="1.2.826.0.1.3680043.2.1611.2.10" />
<code value="denyMedicalDoctorsFromHospitalBToSeeDocuments" />
<display value="I deny Medical Doctors in Hospital B to access my medical record" />
</coding>
</event>
<period>
<start value="2016-05-04T08:18:03.203Z" />
<end value="2016-09-04T08:18:03.203Z" />
</period>
<facilityType>
<coding>
<system value="2.16.840.1.113883.5.10588" />
<code value="HOSP" />
<display value="Hospital" />
</coding>
</facilityType>
<practiceSetting>
<coding>
<system value="2.16.840.1.113883.2.1.6.8" />
<code value="300" />
<display value="General Medicine" />
</coding>
</practiceSetting>
</context>
</DocumentReference>
</resource>
</entry>
<entry>
<resource>
<DocumentReference xmlns="http://hl7.org/fhir">
<id value="doc2" />
<contained>
<Patient xmlns="http://hl7.org/fhir">
<id value="patient" />
<identifier>
<system value="urn:oid:1.3.6.1.4.1.21367.2005.3.7" />
<value value="12345" />
</identifier>
<name>
<use value="official" />
<family value="Baker" />
<given value="Rob" />
</name>
<gender value="male" />
<birthDate value="1970-01-01" />
</Patient>
</contained>
<subject>
<reference value="#patient" />
</subject>
<type>
<coding>
<system value="2.16.840.1.113883.6.1" />
<code value="57016-8" />
<display value="Privacy Policy Acknowledgment" />
</coding>
</type>
<category>
<coding>
<system value="2.16.840.1.113883.6.1" />
<code value="57016-8" />
<display value="Privacy Policy Acknowledgment" />
</coding>
</category>
<author>
<reference value="#patient" />
</author>
<date value="2016-05-04T08:18:03.203Z" />
<status value="current" />
<description value="GPs Privacy Consent" />
<securityLabel>
<coding>
<system value="2.16.840.1.113883.5.25" />
<code value="N" />
<display value="Normal" />
</coding>
</securityLabel>
<content>
<attachment>
<contentType value="text/xml" />
<language value="en-US" />
<url value="urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5" />
</attachment>
<format>
<system value="1.3.6.1.4.1.19376.1.2.3" />
<code value="urn:ihe:iti:bppc-sd:2007" />
<display value="Basic Patient Privacy Consent (scanned part)" />
</format>
</content>
<context>
<event>
<coding>
<system value="1.2.826.0.1.3680043.2.1611.2.10" />
<code value="denyGeneralPractitionersFromHestiaToSeeDocuments" />
<display value="I deny Medical Doctors in Hestia General Practitioners to access my medical record" />
</coding>
</event>
<period>
<start value="2016-05-04T08:18:03.203Z" />
<end value="2016-09-04T08:18:03.203Z" />
</period>
<facilityType>
<coding>
<system value="2.16.840.1.113883.5.10588" />
<code value="HOSP" />
<display value="Hospital" />
</coding>
</facilityType>
<practiceSetting>
<coding>
<system value="2.16.840.1.113883.2.1.6.8" />
<code value="300" />
<display value="General Medicine" />
</coding>
</practiceSetting>
</context>
</DocumentReference>
</resource>
</entry>
</Bundle>

View File

@ -116,6 +116,11 @@
<version>1.4</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<!-- Testing -->
<dependency>

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@ -52,7 +52,7 @@ public class FhirR5 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathR5(theFhirContext);
}

View File

@ -2,8 +2,8 @@ package org.hl7.fhir.r5.hapi.fhirpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
@ -13,7 +13,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine;
import java.util.List;
import java.util.Optional;
public class FhirPathR5 implements IFluentPath {
public class FhirPathR5 implements IFhirPath {
private FHIRPathEngine myEngine;
@ -29,12 +29,12 @@ public class FhirPathR5 implements IFluentPath {
try {
result = myEngine.evaluate((Base) theInput, thePath);
} catch (FHIRException e) {
throw new FluentPathExecutionException(e);
throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}

View File

@ -0,0 +1,58 @@
package ca.uhn.fhir.test.utilities;
/*-
* #%L
* HAPI FHIR Test Utilities
* %%
* Copyright (C) 2014 - 2020 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.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class HttpClientRule implements TestRule {
private CloseableHttpClient myClient;
@Override
public Statement apply(Statement theBase, Description theDescription) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
startClient();
theBase.evaluate();
stopClient();
}
};
}
private void stopClient() throws Exception {
myClient.close();
}
private void startClient() {
myClient = HttpClientBuilder
.create()
.build();
}
public CloseableHttpClient getClient() {
return myClient;
}
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.fluentpath;
package ca.uhn.fhir.fhirpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
@ -21,7 +21,7 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
IFhirPath fp = ourCtx.newFluentPath();
List<HumanName> names = fp.evaluate(p, "Patient.name", HumanName.class);
assertEquals(2, names.size());
assertEquals("N1F1", names.get(0).getFamily());
@ -36,7 +36,7 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
IFhirPath fp = ourCtx.newFluentPath();
List<HumanName> names = fp.evaluate(p, "Patient.nameFOO", HumanName.class);
assertEquals(0, names.size());
}
@ -47,10 +47,10 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
IFhirPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient....nameFOO", HumanName.class);
} catch (FluentPathExecutionException e) {
} catch (FhirPathExecutionException e) {
assertThat(e.getMessage(), containsString("termination at unexpected token"));
}
}
@ -61,10 +61,10 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
IFluentPath fp = ourCtx.newFluentPath();
IFhirPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient.name", StringType.class);
} catch (FluentPathExecutionException e) {
} catch (FhirPathExecutionException e) {
assertEquals("FluentPath expression \"Patient.name\" returned unexpected type HumanName - Expected org.hl7.fhir.dstu3.model.StringType", e.getMessage());
}
}

View File

@ -679,7 +679,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 1, output.getMessages().size());
assertEquals("This \"Patient2 cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
assertEquals("This \"Patient\" cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
ourLog.info(output.getMessages().get(0).getLocationString());
}

View File

@ -23,11 +23,11 @@ package ca.uhn.fhir.model.dstu2;
import java.io.InputStream;
import java.util.Date;
import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.*;
import ca.uhn.fhir.model.dstu2.composite.*;
@ -41,7 +41,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}

10
pom.xml
View File

@ -595,6 +595,10 @@
<organization>Trifork</organization>
<name>Tue Toft Nørgård</name>
</developer>
<developer>
<id>augla</id>
<name>August Langhout</name>
</developer>
</developers>
<licenses>
@ -606,7 +610,7 @@
<properties>
<fhir_core_version>4.2.9-SNAPSHOT</fhir_core_version>
<fhir_core_version>4.2.10-SNAPSHOT</fhir_core_version>
<ucum_version>1.0.2</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>
@ -1512,6 +1516,7 @@
</dependencies>
</dependencyManagement>
<!--
<pluginRepositories>
<pluginRepository>
<id>ossrh</id>
@ -1524,12 +1529,13 @@
<pluginRepository>
<id>maven2</id>
<name>Maven2</name>
<url>http://central.maven.org/maven2/</url>
<url>https://central.maven.org/maven2/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
-->
<build>
<pluginManagement>