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

# Conflicts:
#	hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/CommonConfig.java
#	hapi-fhir-jpaserver-base/pom.xml
#	hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
#	hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
This commit is contained in:
ianmarshall 2020-05-22 17:04:01 -04:00
commit 4d378ee0ed
476 changed files with 18484 additions and 2568 deletions

6
\ Normal file
View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1856
title: The subscription delivery queue in the JPA server was erroniously keeping both a copy of the serialized and the
deserialized payload in memory for each entry in the queue, doubling the memory requirements. This also caused failures
when delivering XML payloads in some configurations. This has been corrected.

View File

@ -31,9 +31,9 @@ jobs:
inputs:
goals: 'clean install'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -nsu -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
mavenOptions: '-Xmx2048m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
mavenOptions: '-Xmx1024m $(MAVEN_OPTS) -Dorg.slf4j.simpleLogger.showDateTime=true -Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss,SSS -Duser.timezone=America/Toronto'
jdkVersionOption: 1.11
- script: bash <(curl https://codecov.io/bash) -t $(CODECOV_TOKEN)
displayName: 'codecov'

View File

@ -1,7 +1,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>5.1.0-SNAPSHOT</version>
@ -147,6 +147,10 @@
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
</ignoredDependencies>
<ignoredResourcePatterns>
<ignoredResourcePattern>.*\.txt$</ignoredResourcePattern>

View File

@ -164,6 +164,9 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
}
if (theClear) {
existingList.clear();
if (theValue == null) {
return;
}
}
existingList.add(theValue);
}

View File

@ -141,10 +141,10 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildren.add(theNext);
}
@Override
public BaseRuntimeChildDefinition getChildByName(String theName){
validateSealed();
BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
return retVal;
return myNameToChild.get(theName);
}
public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException {
@ -156,6 +156,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
return retVal;
}
@Override
public List<BaseRuntimeChildDefinition> getChildren() {
validateSealed();
return myChildren;

View File

@ -66,6 +66,10 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
public abstract ChildTypeEnum getChildType();
public List<BaseRuntimeChildDefinition> getChildren() {
return Collections.emptyList();
}
@SuppressWarnings("unchecked")
private Constructor<T> getConstructor(@Nullable Object theArgument) {
@ -225,6 +229,10 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
}
public BaseRuntimeChildDefinition getChildByName(String theChildName) {
return null;
}
public enum ChildTypeEnum {
COMPOSITE_DATATYPE,
/**

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.VersionUtil;
import ca.uhn.fhir.validation.FhirValidator;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.jena.riot.Lang;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -36,8 +37,18 @@ import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/*
* #%L
@ -177,7 +188,12 @@ public class FhirContext {
ourLog.info("Creating new FhirContext with auto-detected version [{}]. It is recommended to explicitly select a version for future compatibility by invoking FhirContext.forDstuX()",
myVersion.getVersion().name());
} else {
ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
if ("true".equals(System.getProperty("unit_test_mode"))) {
String calledAt = ExceptionUtils.getStackFrames(new Throwable())[4];
ourLog.info("Creating new FHIR context for FHIR version [{}]{}", myVersion.getVersion().name(), calledAt);
} else {
ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
}
}
myResourceTypesToScan = theResourceTypes;
@ -448,6 +464,36 @@ public class FhirContext {
}
/**
* Returns the name of a given resource class.
* @param theResourceType
* @return
*/
public String getResourceType(final Class<? extends IBaseResource> theResourceType) {
return getResourceDefinition(theResourceType).getName();
}
/**
* Returns the name of the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
*/
public String getResourceType(final IBaseResource theResource) {
return getResourceDefinition(theResource).getName();
}
/*
* Returns the type of the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
* <p>
* Note that this method is case insensitive!
* </p>
*
* @throws DataFormatException If the resource name is not known
*/
public String getResourceType(final String theResourceName) throws DataFormatException {
return getResourceDefinition(theResourceName).getName();
}
/*
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
* <p>
@ -476,7 +522,6 @@ public class FhirContext {
retVal = scanResourceType(clazz);
}
}
return retVal;
}
@ -501,8 +546,10 @@ public class FhirContext {
/**
* Returns an unmodifiable set containing all resource names known to this
* context
*
* @since 5.1.0
*/
public Set<String> getResourceNames() {
public Set<String> getResourceTypes() {
Set<String> resourceNames = new HashSet<>();
if (myNameToResourceDefinition.isEmpty()) {

View File

@ -0,0 +1,131 @@
package ca.uhn.fhir.context;
/*-
* #%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 com.google.common.collect.Sets;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RuntimeChildExt extends BaseRuntimeChildDefinition {
private Map<String, BaseRuntimeElementDefinition<?>> myNameToChild;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToChild;
private Map<Class<? extends IBase>, String> myDatatypeToChildName;
@Override
public IAccessor getAccessor() {
return new IAccessor() {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public List<IBase> getValues(IBase theTarget) {
List extension = ((IBaseHasExtensions) theTarget).getExtension();
return Collections.unmodifiableList(extension);
}
};
}
@Override
public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
return myNameToChild.get(theName);
}
@Override
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theType) {
return myDatatypeToChild.get(theType);
}
@Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
return myDatatypeToChildName.get(theDatatype);
}
@Override
public String getElementName() {
return "extension";
}
@Override
public int getMax() {
return -1;
}
@Override
public int getMin() {
return 0;
}
@Override
public IMutator getMutator() {
return new IMutator() {
@Override
public void addValue(IBase theTarget, IBase theValue) {
List extensions = ((IBaseHasExtensions) theTarget).getExtension();
IBaseExtension<?, ?> value = (IBaseExtension<?, ?>) theValue;
extensions.add(value);
}
@Override
public void setValue(IBase theTarget, IBase theValue) {
List extensions = ((IBaseHasExtensions) theTarget).getExtension();
extensions.clear();
if (theValue != null) {
IBaseExtension<?, ?> value = (IBaseExtension<?, ?>) theValue;
extensions.add(value);
}
}
};
}
@Override
public Set<String> getValidChildNames() {
return Sets.newHashSet("extension");
}
@Override
public boolean isSummary() {
return false;
}
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myNameToChild = new HashMap<>();
myDatatypeToChild = new HashMap<>();
myDatatypeToChildName = new HashMap<>();
for (BaseRuntimeElementDefinition<?> next : theClassToElementDefinitions.values()) {
if (next.getName().equals("Extension")) {
myNameToChild.put("extension", next);
myDatatypeToChild.put(next.getImplementingClass(), next);
myDatatypeToChildName.put(next.getImplementingClass(), "extension");
}
}
Validate.isTrue(!myNameToChild.isEmpty());
}
}

View File

@ -23,10 +23,14 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -38,6 +42,8 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
private BaseRuntimeElementDefinition<?> myProfileOf;
private Class<? extends IBaseDatatype> myProfileOfType;
private boolean mySpecialization;
private List<BaseRuntimeChildDefinition> myChildren;
private RuntimeChildExt myRuntimeChildExt;
public RuntimePrimitiveDatatypeDefinition(DatatypeDef theDef, Class<? extends IPrimitiveType<?>> theImplementingClass, boolean theStandardType) {
super(theDef.name(), theImplementingClass, theStandardType);
@ -56,6 +62,19 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
determineNativeType(theImplementingClass);
}
@Override
public List<BaseRuntimeChildDefinition> getChildren() {
return myChildren;
}
@Override
public BaseRuntimeChildDefinition getChildByName(String theChildName) {
if ("extension".equals(theChildName)) {
return myRuntimeChildExt;
}
return null;
}
private void determineNativeType(Class<? extends IPrimitiveType<?>> theImplementingClass) {
Class<?> clazz = theImplementingClass;
while (clazz.equals(Object.class) == false) {
@ -126,6 +145,14 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
throw new ConfigurationException(b.toString());
}
}
myRuntimeChildExt = new RuntimeChildExt();
myRuntimeChildExt.sealAndInitialize(theContext, theClassToElementDefinitions);
myChildren = new ArrayList<>();
myChildren.addAll(super.getChildren());
myChildren.add(myRuntimeChildExt);
myChildren = Collections.unmodifiableList(myChildren);
}
}

View File

@ -306,7 +306,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
List<IBaseResource> resources = parseBundle(reader);
for (IBaseResource next : resources) {
String nextType = getFhirContext().getResourceDefinition(next).getName();
String nextType = getFhirContext().getResourceType(next);
if ("StructureDefinition".equals(nextType)) {
String url = getConformanceResourceUrl(next);

View File

@ -125,7 +125,7 @@ public interface IValidationSupport {
Validate.notNull(theClass, "theClass must not be null or blank");
Validate.notBlank(theUri, "theUri must not be null or blank");
switch (getFhirContext().getResourceDefinition(theClass).getName()) {
switch (getFhirContext().getResourceType(theClass)) {
case "StructureDefinition":
return theClass.cast(fetchStructureDefinition(theUri));
case "ValueSet":

View File

@ -20,15 +20,15 @@ package ca.uhn.fhir.fhirpath;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBase;
import java.util.List;
import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase;
public interface IFhirPath {
/**
* Apply the given FluentPath expression against the given input and return
* Apply the given FhirPath expression against the given input and return
* all results in a list
*
* @param theInput The input object (generally a resource or datatype)
@ -38,7 +38,7 @@ public interface IFhirPath {
<T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType);
/**
* Apply the given FluentPath expression against the given input and return
* Apply the given FhirPath expression against the given input and return
* the first match (if any)
*
* @param theInput The input object (generally a resource or datatype)

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.interceptor.api;
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@ -30,6 +31,7 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface Interceptor {
/**

View File

@ -757,6 +757,46 @@ public enum Pointcut {
*/
SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"),
/**
* <b>Subscription Hook:</b>
* Invoked immediately after the delivery of MESSAGE subscription.
* <p>
* When this hook is called, all processing is complete so this hook should not
* make any changes to the parameters.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription</li>
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage</li>
* </ul>
* <p>
* Hooks should return <code>void</code>.
* </p>
*/
SUBSCRIPTION_AFTER_MESSAGE_DELIVERY(void.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"),
/**
* <b>Subscription Hook:</b>
* Invoked immediately before the delivery of a MESSAGE subscription.
* <p>
* Hooks may make changes to the delivery payload, or make changes to the
* canonical subscription such as adding headers, modifying the channel
* endpoint, etc.
* </p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription</li>
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage</li>
* </ul>
* <p>
* Hooks may return <code>void</code> or may return a <code>boolean</code>. If the method returns
* <code>void</code> or <code>true</code>, processing will continue normally. If the method
* returns <code>false</code>, processing will be aborted.
* </p>
*/
SUBSCRIPTION_BEFORE_MESSAGE_DELIVERY(boolean.class, "ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription", "ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage"),
/**
* <b>Subscription Hook:</b>
* Invoked whenever a persisted resource (a resource that has just been stored in the
@ -1244,7 +1284,7 @@ public enum Pointcut {
/**
* <b>Storage Hook:</b>
* Invoked before a resource will be created
* Invoked before a resource will be deleted
* <p>
* Hooks will have access to the contents of the resource being deleted
* but should not make any changes as storage has already occurred
@ -1282,7 +1322,7 @@ public enum Pointcut {
/**
* <b>Storage Hook:</b>
* Invoked when a resource delete operation is about to fail due to referential integrity hcts.
* Invoked when a resource delete operation is about to fail due to referential integrity checks. Intended for use with {@literal ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor}.
* <p>
* Hooks will have access to the list of resources that have references to the resource being deleted.
* </p>
@ -1522,6 +1562,23 @@ public enum Pointcut {
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
),
/**
* <b>EMPI Hook:</b>
* Invoked whenever a persisted Patient/Practitioner resource (a resource that has just been stored in the
* database via a create/update/patch/etc.) has been matched against related resources and EMPI links have been updated.
* <p>
* Hooks may accept the following parameters:
* <ul>
* <li>ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage - This parameter should not be modified as processing is complete when this hook is invoked.</li>
* <li>ca.uhn.fhir.empi.model.TransactionLogMessages - This parameter is for informational messages provided by the EMPI module during EMPI procesing. .</li>
* </ul>
* </p>
* <p>
* Hooks should return <code>void</code>.
* </p>
*/
EMPI_AFTER_PERSISTED_RESOURCE_CHECKED(void.class, "ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage", "ca.uhn.fhir.rest.server.TransactionLogMessages"),
/**
* <b>Performance Tracing Hook:</b>
* This hook is invoked when any informational messages generated by the

View File

@ -73,7 +73,7 @@ public abstract class BaseNarrativeGenerator implements INarrativeGenerator {
}
boolean retVal = false;
String resourceName = theFhirContext.getResourceDefinition(theResource).getName();
String resourceName = theFhirContext.getResourceType(theResource);
String contextPath = defaultIfEmpty(theTemplate.getContextPath(), resourceName);
// Narrative templates define a path within the resource that they apply to. Here, we're

View File

@ -20,8 +20,19 @@ package ca.uhn.fhir.parser;
* #L%
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildContainedResources;
import ca.uhn.fhir.context.RuntimeChildDirectResource;
import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IIdentifiableElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
@ -29,6 +40,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.path.EncodeContextPath;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.BundleUtil;
@ -36,9 +48,19 @@ import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nullable;
import java.io.IOException;
@ -49,7 +71,17 @@ import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -73,8 +105,8 @@ public abstract class BaseParser implements IParser {
private ContainedResources myContainedResources;
private boolean myEncodeElementsAppliesToChildResourcesOnly;
private FhirContext myContext;
private List<ElementsPath> myDontEncodeElements;
private List<ElementsPath> myEncodeElements;
private List<EncodeContextPath> myDontEncodeElements;
private List<EncodeContextPath> myEncodeElements;
private Set<String> myEncodeElementsAppliesToResourceTypes;
private IIdType myEncodeForceResourceId;
private IParserErrorHandler myErrorHandler;
@ -95,7 +127,7 @@ public abstract class BaseParser implements IParser {
myErrorHandler = theParserErrorHandler;
}
List<ElementsPath> getDontEncodeElements() {
List<EncodeContextPath> getDontEncodeElements() {
return myDontEncodeElements;
}
@ -106,13 +138,13 @@ public abstract class BaseParser implements IParser {
} else {
myDontEncodeElements = theDontEncodeElements
.stream()
.map(ElementsPath::new)
.map(EncodeContextPath::new)
.collect(Collectors.toList());
}
return this;
}
List<ElementsPath> getEncodeElements() {
List<EncodeContextPath> getEncodeElements() {
return myEncodeElements;
}
@ -125,7 +157,7 @@ public abstract class BaseParser implements IParser {
} else {
myEncodeElements = theEncodeElements
.stream()
.map(ElementsPath::new)
.map(EncodeContextPath::new)
.collect(Collectors.toList());
myEncodeElementsAppliesToResourceTypes = new HashSet<>();
@ -413,7 +445,7 @@ public abstract class BaseParser implements IParser {
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
}
String resourceName = myContext.getResourceDefinition(theResource).getName();
String resourceName = myContext.getResourceType(theResource);
theEncodeContext.pushPath(resourceName, true);
doEncodeResourceToWriter(theResource, theWriter, theEncodeContext);
@ -963,7 +995,7 @@ public abstract class BaseParser implements IParser {
retVal = false;
} else {
if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName();
String resourceName = myContext.getResourceType(theResource);
if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + ".id"))) {
retVal = false;
} else if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath("*.id"))) {
@ -988,7 +1020,7 @@ public abstract class BaseParser implements IParser {
*/
protected boolean shouldEncodePath(IResource theResource, String thePath) {
if (myDontEncodeElements != null) {
String resourceName = myContext.getResourceDefinition(theResource).getName();
String resourceName = myContext.getResourceType(theResource);
if (myDontEncodeElements.stream().anyMatch(t -> t.equalsPath(resourceName + "." + thePath))) {
return false;
} else return myDontEncodeElements.stream().noneMatch(t -> t.equalsPath("*." + thePath));
@ -1018,7 +1050,7 @@ public abstract class BaseParser implements IParser {
protected boolean shouldEncodeResource(String theName) {
if (myDontEncodeElements != null) {
for (ElementsPath next : myDontEncodeElements) {
for (EncodeContextPath next : myDontEncodeElements) {
if (next.equalsPath(theName)) {
return false;
}
@ -1047,6 +1079,20 @@ public abstract class BaseParser implements IParser {
}
/**
* EncodeContext is a shared state object that is passed around the
* encode process
*/
public class EncodeContext extends EncodeContextPath {
private final Map<Key, List<BaseParser.CompositeChildElement>> myCompositeChildrenCache = new HashMap<>();
public Map<Key, List<BaseParser.CompositeChildElement>> getCompositeChildrenCache() {
return myCompositeChildrenCache;
}
}
protected class CompositeChildElement {
private final BaseRuntimeChildDefinition myDef;
private final CompositeChildElement myParent;
@ -1127,7 +1173,7 @@ public abstract class BaseParser implements IParser {
}
private boolean checkIfParentShouldBeEncodedAndBuildPath() {
List<ElementsPath> encodeElements = myEncodeElements;
List<EncodeContextPath> encodeElements = myEncodeElements;
String currentResourceName = myEncodeContext.getResourcePath().get(myEncodeContext.getResourcePath().size() - 1).getName();
if (myEncodeElementsAppliesToResourceTypes != null && !myEncodeElementsAppliesToResourceTypes.contains(currentResourceName)) {
@ -1158,7 +1204,7 @@ public abstract class BaseParser implements IParser {
return checkIfPathMatchesForEncoding(myDontEncodeElements, false);
}
private boolean checkIfPathMatchesForEncoding(List<ElementsPath> theElements, boolean theCheckingForEncodeElements) {
private boolean checkIfPathMatchesForEncoding(List<EncodeContextPath> theElements, boolean theCheckingForEncodeElements) {
boolean retVal = false;
if (myDef != null) {
@ -1172,9 +1218,9 @@ public abstract class BaseParser implements IParser {
} else {
EncodeContextPath currentResourcePath = myEncodeContext.getCurrentResourcePath();
ourLog.trace("Current resource path: {}", currentResourcePath);
for (ElementsPath next : theElements) {
for (EncodeContextPath next : theElements) {
if (next.startsWith(currentResourcePath)) {
if (next.startsWith(currentResourcePath, true)) {
if (theCheckingForEncodeElements || next.getPath().size() == currentResourcePath.getPath().size()) {
retVal = true;
break;
@ -1272,225 +1318,13 @@ public abstract class BaseParser implements IParser {
}
}
protected class EncodeContextPath {
private final List<EncodeContextPathElement> myPath;
public EncodeContextPath() {
myPath = new ArrayList<>(10);
}
public EncodeContextPath(List<EncodeContextPathElement> thePath) {
myPath = thePath;
}
@Override
public String toString() {
return myPath.stream().map(t -> t.toString()).collect(Collectors.joining("."));
}
protected List<EncodeContextPathElement> getPath() {
return myPath;
}
public EncodeContextPath getCurrentResourcePath() {
EncodeContextPath retVal = null;
for (int i = myPath.size() - 1; i >= 0; i--) {
if (myPath.get(i).isResource()) {
retVal = new EncodeContextPath(myPath.subList(i, myPath.size()));
break;
}
}
Validate.isTrue(retVal != null);
return retVal;
}
}
protected class ElementsPath extends EncodeContextPath {
protected ElementsPath(String thePath) {
StringTokenizer tok = new StringTokenizer(thePath, ".");
boolean first = true;
while (tok.hasMoreTokens()) {
String next = tok.nextToken();
if (first && next.equals("*")) {
getPath().add(new EncodeContextPathElement("*", true));
} else if (isNotBlank(next)) {
getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0))));
}
first = false;
}
}
public boolean startsWith(EncodeContextPath theCurrentResourcePath) {
for (int i = 0; i < getPath().size(); i++) {
if (theCurrentResourcePath.getPath().size() == i) {
return true;
}
EncodeContextPathElement expected = getPath().get(i);
EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i);
if (!expected.matches(actual)) {
return false;
}
}
return true;
}
public boolean equalsPath(String thePath) {
ElementsPath parsedPath = new ElementsPath(thePath);
return getPath().equals(parsedPath.getPath());
}
}
/**
* EncodeContext is a shared state object that is passed around the
* encode process
*/
protected class EncodeContext extends EncodeContextPath {
private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10);
private final Map<Key, List<CompositeChildElement>> myCompositeChildrenCache = new HashMap<>();
public Map<Key, List<CompositeChildElement>> getCompositeChildrenCache() {
return myCompositeChildrenCache;
}
protected ArrayList<EncodeContextPathElement> getResourcePath() {
return myResourcePath;
}
public String getLeafElementName() {
return getPath().get(getPath().size() - 1).getName();
}
public String getLeafResourceName() {
return myResourcePath.get(myResourcePath.size() - 1).getName();
}
public String getLeafResourcePathFirstField() {
String retVal = null;
for (int i = getPath().size() - 1; i >= 0; i--) {
if (getPath().get(i).isResource()) {
break;
} else {
retVal = getPath().get(i).getName();
}
}
return retVal;
}
/**
* Add an element at the end of the path
*/
protected void pushPath(String thePathElement, boolean theResource) {
assert isNotBlank(thePathElement);
assert !thePathElement.contains(".");
assert theResource ^ Character.isLowerCase(thePathElement.charAt(0));
EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource);
getPath().add(element);
if (theResource) {
myResourcePath.add(element);
}
}
/**
* Remove the element at the end of the path
*/
public void popPath() {
EncodeContextPathElement removed = getPath().remove(getPath().size() - 1);
if (removed.isResource()) {
myResourcePath.remove(myResourcePath.size() - 1);
}
}
}
protected class EncodeContextPathElement {
private final String myName;
private final boolean myResource;
public EncodeContextPathElement(String theName, boolean theResource) {
Validate.notBlank(theName);
myName = theName;
myResource = theResource;
}
public boolean matches(EncodeContextPathElement theOther) {
if (myResource != theOther.isResource()) {
return false;
}
String otherName = theOther.getName();
if (myName.equals(otherName)) {
return true;
}
/*
* This is here to handle situations where a path like
* Observation.valueQuantity has been specified as an include/exclude path,
* since we only know that path as
* Observation.value
* until we get to actually looking at the values there.
*/
if (myName.length() > otherName.length() && myName.startsWith(otherName)) {
char ch = myName.charAt(otherName.length());
if (Character.isUpperCase(ch)) {
return true;
}
}
return myName.equals("*");
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
EncodeContextPathElement that = (EncodeContextPathElement) theO;
return new EqualsBuilder()
.append(myResource, that.myResource)
.append(myName, that.myName)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myName)
.append(myResource)
.toHashCode();
}
@Override
public String toString() {
if (myResource) {
return myName + "(res)";
}
return myName;
}
public String getName() {
return myName;
}
public boolean isResource() {
return myResource;
}
}
private static class Key {
private final BaseRuntimeElementCompositeDefinition<?> resDef;
private final boolean theContainedResource;
private final CompositeChildElement theParent;
private final EncodeContext theEncodeContext;
private final BaseParser.CompositeChildElement theParent;
private final BaseParser.EncodeContext theEncodeContext;
public Key(BaseRuntimeElementCompositeDefinition<?> resDef, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {
public Key(BaseRuntimeElementCompositeDefinition<?> resDef, final boolean theContainedResource, final BaseParser.CompositeChildElement theParent, BaseParser.EncodeContext theEncodeContext) {
this.resDef = resDef;
this.theContainedResource = theContainedResource;
this.theParent = theParent;
@ -1524,6 +1358,7 @@ public abstract class BaseParser implements IParser {
}
}
static class ContainedResources {
private long myNextContainedId = 1;

View File

@ -104,12 +104,16 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem,
CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) {
CompositeChildElement theParent, EncodeContext theEncodeContext, boolean theContainedResource, IBase theContainingElement) {
boolean retVal = false;
if (ext.size() > 0) {
Boolean encodeExtension = null;
for (IBaseExtension<?, ?> next : ext) {
if (next.isEmpty()) {
continue;
}
// Make sure we respect _summary and _elements
if (encodeExtension == null) {
encodeExtension = isEncodeExtension(theParent, theEncodeContext, theContainedResource, theContainingElement);
@ -621,7 +625,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
EncodeContext encodeContext = new EncodeContext();
String resourceName = myContext.getResourceDefinition(theResource).getName();
String resourceName = myContext.getResourceType(theResource);
encodeContext.pushPath(resourceName, true);
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter, encodeContext);
}

View File

@ -20,8 +20,18 @@ package ca.uhn.fhir.parser;
* #L%
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IMutator;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildDeclaredExtensionDefinition;
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition;
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition;
import ca.uhn.fhir.context.RuntimeResourceBlockDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IIdentifiableElement;
@ -45,7 +55,22 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
@ -1055,7 +1080,7 @@ class ParserState<T> {
}
private void stitchBundleCrossReferences() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
final boolean bundle = "Bundle".equals(myContext.getResourceType(myInstance));
if (bundle) {
FhirTerser t = myContext.newTerser();
@ -1078,7 +1103,7 @@ class ParserState<T> {
for (IBaseResource next : myGlobalResources) {
IIdType id = next.getIdElement();
if (id != null && !id.isEmpty()) {
String resName = myContext.getResourceDefinition(next).getName();
String resName = myContext.getResourceType(next);
IIdType idType = id.withResourceType(resName).toUnqualifiedVersionless();
idToResource.put(idType.getValueAsString(), next);
}
@ -1172,7 +1197,7 @@ class ParserState<T> {
IResource nextResource = (IResource) getCurrentElement();
String version = ResourceMetadataKeyEnum.VERSION.get(nextResource);
String resourceName = myContext.getResourceDefinition(nextResource).getName();
String resourceName = myContext.getResourceType(nextResource);
String bundleIdPart = nextResource.getId().getIdPart();
if (isNotBlank(bundleIdPart)) {
// if (isNotBlank(entryBaseUrl)) {
@ -1221,7 +1246,7 @@ class ParserState<T> {
if (getCurrentElement() instanceof IDomainResource) {
IDomainResource elem = (IDomainResource) getCurrentElement();
String resourceName = myContext.getResourceDefinition(elem).getName();
String resourceName = myContext.getResourceType(elem);
String versionId = elem.getMeta().getVersionId();
if (StringUtils.isBlank(elem.getIdElement().getIdPart())) {
// Resource has no ID

View File

@ -288,7 +288,7 @@ public class RDFParser extends BaseParser {
}
case RESOURCE: {
IBaseResource baseResource = (IBaseResource) element;
String resourceName = this.context.getResourceDefinition(baseResource).getName();
String resourceName = this.context.getResourceType(baseResource);
if (!super.shouldEncodeResource(resourceName)) {
break;
}

View File

@ -295,7 +295,7 @@ public class XmlParser extends BaseParser {
}
case RESOURCE: {
IBaseResource resource = (IBaseResource) theElement;
String resourceName = myContext.getResourceDefinition(resource).getName();
String resourceName = myContext.getResourceType(resource);
if (!super.shouldEncodeResource(resourceName)) {
break;
}

View File

@ -0,0 +1,161 @@
package ca.uhn.fhir.parser.path;
/*-
* #%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 org.apache.commons.lang3.Validate;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class EncodeContextPath {
private final List<EncodeContextPathElement> myPath;
private final ArrayList<EncodeContextPathElement> myResourcePath = new ArrayList<>(10);
public EncodeContextPath() {
this(new ArrayList<>(10));
}
public EncodeContextPath(String thePath) {
this();
StringTokenizer tok = new StringTokenizer(thePath, ".");
boolean first = true;
while (tok.hasMoreTokens()) {
String next = tok.nextToken();
if (first && next.equals("*")) {
getPath().add(new EncodeContextPathElement("*", true));
} else if (isNotBlank(next)) {
getPath().add(new EncodeContextPathElement(next, Character.isUpperCase(next.charAt(0))));
}
first = false;
}
}
public EncodeContextPath(List<EncodeContextPathElement> thePath) {
myPath = thePath;
}
@Override
public String toString() {
return myPath.stream().map(t -> t.toString()).collect(Collectors.joining("."));
}
public List<EncodeContextPathElement> getPath() {
return myPath;
}
public EncodeContextPath getCurrentResourcePath() {
EncodeContextPath retVal = null;
for (int i = myPath.size() - 1; i >= 0; i--) {
if (myPath.get(i).isResource()) {
retVal = new EncodeContextPath(myPath.subList(i, myPath.size()));
break;
}
}
Validate.isTrue(retVal != null);
return retVal;
}
/**
* Add an element at the end of the path
*/
public void pushPath(String thePathElement, boolean theResource) {
assert isNotBlank(thePathElement);
assert !thePathElement.contains(".");
assert theResource ^ Character.isLowerCase(thePathElement.charAt(0));
EncodeContextPathElement element = new EncodeContextPathElement(thePathElement, theResource);
getPath().add(element);
if (theResource) {
myResourcePath.add(element);
}
}
/**
* Remove the element at the end of the path
*/
public void popPath() {
EncodeContextPathElement removed = getPath().remove(getPath().size() - 1);
if (removed.isResource()) {
myResourcePath.remove(myResourcePath.size() - 1);
}
}
public ArrayList<EncodeContextPathElement> getResourcePath() {
return myResourcePath;
}
public String getLeafElementName() {
return getPath().get(getPath().size() - 1).getName();
}
public String getLeafResourceName() {
return myResourcePath.get(myResourcePath.size() - 1).getName();
}
public String getLeafResourcePathFirstField() {
String retVal = null;
for (int i = getPath().size() - 1; i >= 0; i--) {
if (getPath().get(i).isResource()) {
break;
} else {
retVal = getPath().get(i).getName();
}
}
return retVal;
}
/**
* Tests and returns whether this path starts with {@literal theCurrentResourcePath}
*
* @param theCurrentResourcePath The path to test
* @param theAllowSymmmetrical If <code>true</code>, this method will return true if {@literal theCurrentResourcePath} starts with this path as well as testing whether this path starts with {@literal theCurrentResourcePath}
*/
public boolean startsWith(EncodeContextPath theCurrentResourcePath, boolean theAllowSymmmetrical) {
for (int i = 0; i < getPath().size(); i++) {
if (theCurrentResourcePath.getPath().size() == i) {
return true;
}
EncodeContextPathElement expected = getPath().get(i);
EncodeContextPathElement actual = theCurrentResourcePath.getPath().get(i);
if (!expected.matches(actual)) {
return false;
}
}
if (theAllowSymmmetrical) {
return true;
}
return getPath().size() == theCurrentResourcePath.getPath().size();
}
public boolean equalsPath(String thePath) {
EncodeContextPath parsedPath = new EncodeContextPath(thePath);
return getPath().equals(parsedPath.getPath());
}
}

View File

@ -0,0 +1,103 @@
package ca.uhn.fhir.parser.path;
/*-
* #%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 org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class EncodeContextPathElement {
private final String myName;
private final boolean myResource;
public EncodeContextPathElement(String theName, boolean theResource) {
Validate.notBlank(theName);
myName = theName;
myResource = theResource;
}
public boolean matches(EncodeContextPathElement theOther) {
if (myResource != theOther.isResource()) {
return false;
}
String otherName = theOther.getName();
if (myName.equals(otherName)) {
return true;
}
/*
* This is here to handle situations where a path like
* Observation.valueQuantity has been specified as an include/exclude path,
* since we only know that path as
* Observation.value
* until we get to actually looking at the values there.
*/
if (myName.length() > otherName.length() && myName.startsWith(otherName)) {
char ch = myName.charAt(otherName.length());
if (Character.isUpperCase(ch)) {
return true;
}
}
return myName.equals("*") || otherName.equals("*");
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
return false;
}
EncodeContextPathElement that = (EncodeContextPathElement) theO;
return new EqualsBuilder()
.append(myResource, that.myResource)
.append(myName, that.myName)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myName)
.append(myResource)
.toHashCode();
}
@Override
public String toString() {
if (myResource) {
return myName + "(res)";
}
return myName;
}
public String getName() {
return myName;
}
public boolean isResource() {
return myResource;
}
}

View File

@ -20,18 +20,29 @@ package ca.uhn.fhir.rest.api;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Patch;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.UrlUtil;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* Parameter type for methods annotated with {@link Patch}
*/
public enum PatchTypeEnum {
JSON_PATCH(Constants.CT_JSON_PATCH),
XML_PATCH(Constants.CT_XML_PATCH);
XML_PATCH(Constants.CT_XML_PATCH),
FHIR_PATCH_JSON(Constants.CT_FHIR_JSON_NEW),
FHIR_PATCH_XML(Constants.CT_FHIR_XML_NEW);
private static volatile Map<String, PatchTypeEnum> ourContentTypeToPatchType;
private final String myContentType;
PatchTypeEnum(String theContentType) {
@ -42,19 +53,36 @@ public enum PatchTypeEnum {
return myContentType;
}
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(String theContentType) {
String contentType = theContentType;
@Nonnull
public static PatchTypeEnum forContentTypeOrThrowInvalidRequestException(FhirContext theContext, String theContentType) {
String contentType = defaultString(theContentType);
int semiColonIdx = contentType.indexOf(';');
if (semiColonIdx != -1) {
contentType = theContentType.substring(0, semiColonIdx);
}
contentType = contentType.trim();
if (Constants.CT_JSON_PATCH.equals(contentType)) {
return JSON_PATCH;
} else if (Constants.CT_XML_PATCH.equals(contentType)) {
return XML_PATCH;
} else {
throw new InvalidRequestException("Invalid Content-Type for PATCH operation: " + UrlUtil.sanitizeUrlPart(theContentType));
Map<String, PatchTypeEnum> map = ourContentTypeToPatchType;
if (map == null) {
map = new HashMap<>();
for (PatchTypeEnum next : values()) {
map.put(next.getContentType(), next);
}
ourContentTypeToPatchType = map;
}
PatchTypeEnum retVal = map.get(contentType);
if (retVal == null) {
if (isBlank(contentType)) {
String msg = theContext.getLocalizer().getMessage(PatchTypeEnum.class, "missingPatchContentType");
throw new InvalidRequestException(msg);
}
String msg = theContext.getLocalizer().getMessageSanitized(PatchTypeEnum.class, "invalidPatchContentType", contentType);
throw new InvalidRequestException(msg);
}
return retVal;
}
}

View File

@ -51,4 +51,13 @@ public interface IBaseOn<T> {
*/
T onInstance(IIdType theId);
/**
* Perform the operation across all versions of a specific resource (by ID and type) on the server.
* Note that <code>theId</code> must be populated with both a resource type and a resource ID at
* a minimum.
*
* @throws IllegalArgumentException If <code>theId</code> does not contain at least a resource type and ID
*/
T onInstance(String theId);
}

View File

@ -61,4 +61,5 @@ public interface IOperation extends IBaseOn<IOperationUnnamed> {
* using FHIR Resources</a>
*/
IOperationProcessMsg processMessage();
}

View File

@ -20,15 +20,23 @@ package ca.uhn.fhir.rest.gclient;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IPatch {
/**
* The body of the patch document serialized in either XML or JSON which conforms to
* http://jsonpatch.com/ or http://tools.ietf.org/html/rfc5261
*
* @param thePatchBody
* The body of the patch
*
* @param thePatchBody The body of the patch
*/
IPatchWithBody withBody(String thePatchBody);
/**
* The body of the patch document using FHIR Patch syntax as described at
* http://hl7.org/fhir/fhirpatch.html
*
* @since 5.1.0
*/
IPatchWithBody withFhirPatch(IBaseParameters thePatchBody);
}

View File

@ -28,11 +28,11 @@ public interface IPatchWithBody extends IPatchExecutable {
/**
* Build a conditional URL using fluent constants on resource types
*
*
* @param theResourceType
* The resource type to patch (e.g. "Patient.class")
*/
IPatchWithQuery conditional(Class<? extends IBaseResource> theClass);
IPatchWithQuery conditional(Class<? extends IBaseResource> theResourceType);
/**
* Build a conditional URL using fluent constants on resource types
@ -53,12 +53,12 @@ public interface IPatchWithBody extends IPatchExecutable {
IPatchExecutable conditionalByUrl(String theSearchUrl);
/**
* The resource ID to patch
* The resource ID to patch (must include both a resource type and an ID, e.g. <code>Patient/123</code>)
*/
IPatchExecutable withId(IIdType theId);
/**
* The resource ID to patch
* The resource ID to patch (must include both a resource type and an ID, e.g. <code>Patient/123</code>)
*/
IPatchExecutable withId(String theId);

View File

@ -10,11 +10,9 @@ import ca.uhn.fhir.util.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;

View File

@ -267,12 +267,12 @@ public class BundleUtil {
* <code>Bundle.entry.resource</code> is a Binary resource with a patch
* payload type.
*/
public static boolean isDstu3TransactionPatch(IBaseResource thePayloadResource) {
public static boolean isDstu3TransactionPatch(FhirContext theContext, IBaseResource thePayloadResource) {
boolean isPatch = false;
if (thePayloadResource instanceof IBaseBinary) {
String contentType = ((IBaseBinary) thePayloadResource).getContentType();
try {
PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(theContext, contentType);
isPatch = true;
} catch (InvalidRequestException e) {
// ignore

View File

@ -1,7 +1,17 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildDirectResource;
import ca.uhn.fhir.context.RuntimeExtensionDtDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
@ -10,14 +20,30 @@ import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
@ -241,6 +267,11 @@ public class FhirTerser {
return retVal.get(0);
}
public <T extends IBase> Optional<T> getSingleValue(IBase theTarget, String thePath, Class<T> theWantedType) {
return Optional.ofNullable(getSingleValueOrNull(theTarget, thePath, theWantedType));
}
private <T extends IBase> List<T> getValues(BaseRuntimeElementCompositeDefinition<?> theCurrentDef, IBase theCurrentObj, List<String> theSubList, Class<T> theWantedClass) {
return getValues(theCurrentDef, theCurrentObj, theSubList, theWantedClass, false, false);
}
@ -471,85 +502,85 @@ public class FhirTerser {
* Returns values stored in an element identified by its path. The list of values is of
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.@param theElement The resource instance to be accessed. Must not be null.
* @return A list of values of type {@link Object}.
*/
public List<IBase> getValues(IBaseResource theResource, String thePath) {
public List<IBase> getValues(IBase theElement, String thePath) {
Class<IBase> wantedClass = IBase.class;
return getValues(theResource, thePath, wantedClass);
return getValues(theElement, thePath, wantedClass);
}
/**
* Returns values stored in an element identified by its path. The list of values is of
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @return A list of values of type {@link Object}.
*/
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate) {
public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate) {
Class<IBase> wantedClass = IBase.class;
return getValues(theResource, thePath, wantedClass, theCreate);
return getValues(theElement, thePath, wantedClass, theCreate);
}
/**
* Returns values stored in an element identified by its path. The list of values is of
* type {@link Object}.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param theAddExtension When set to <code>true</code>, the terser will add a null-valued extension where one or more such extensions already exist.
* @return A list of values of type {@link Object}.
*/
public List<IBase> getValues(IBaseResource theResource, String thePath, boolean theCreate, boolean theAddExtension) {
public List<IBase> getValues(IBase theElement, String thePath, boolean theCreate, boolean theAddExtension) {
Class<IBase> wantedClass = IBase.class;
return getValues(theResource, thePath, wantedClass, theCreate, theAddExtension);
return getValues(theElement, thePath, wantedClass, theCreate, theAddExtension);
}
/**
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
List<String> parts = parsePath(def, thePath);
return getValues(def, theResource, parts, theWantedClass);
return getValues(def, theElement, parts, theWantedClass);
}
/**
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
List<String> parts = parsePath(def, thePath);
return getValues(def, theResource, parts, theWantedClass, theCreate, false);
return getValues(def, theElement, parts, theWantedClass, theCreate, false);
}
/**
* Returns values stored in an element identified by its path. The list of values is of
* type <code>theWantedClass</code>.
*
* @param theResource The resource instance to be accessed. Must not be null.
* @param theElement The element to be accessed. Must not be null.
* @param thePath The path for the element to be accessed.
* @param theWantedClass The desired class to be returned in a list.
* @param theCreate When set to <code>true</code>, the terser will create a null-valued element where none exists.
@ -557,10 +588,10 @@ public class FhirTerser {
* @param <T> Type declared by <code>theWantedClass</code>
* @return A list of values of type <code>theWantedClass</code>.
*/
public <T extends IBase> List<T> getValues(IBaseResource theResource, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
public <T extends IBase> List<T> getValues(IBase theElement, String thePath, Class<T> theWantedClass, boolean theCreate, boolean theAddExtension) {
BaseRuntimeElementCompositeDefinition<?> def = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theElement.getClass());
List<String> parts = parsePath(def, thePath);
return getValues(def, theResource, parts, theWantedClass, theCreate, theAddExtension);
return getValues(def, theElement, parts, theWantedClass, theCreate, theAddExtension);
}
private List<String> parsePath(BaseRuntimeElementCompositeDefinition<?> theElementDef, String thePath) {
@ -656,7 +687,7 @@ public class FhirTerser {
IBaseResource nextTarget = nextValue.getResource();
nextTargetId = nextTarget.getIdElement().toUnqualifiedVersionless();
if (!nextTargetId.hasResourceType()) {
String resourceType = myContext.getResourceDefinition(nextTarget).getName();
String resourceType = myContext.getResourceType(nextTarget);
nextTargetId.setParts(null, resourceType, nextTargetId.getIdPart(), null);
}
nextRef = nextTargetId.getValue();
@ -801,7 +832,7 @@ public class FhirTerser {
}
/**
* Visit all elements in a given resource
* Visit all elements in a given resource or element
* <p>
* <b>THIS ALTERNATE METHOD IS STILL EXPERIMENTAL! USE WITH CAUTION</b>
* </p>
@ -810,12 +841,19 @@ public class FhirTerser {
* {@link BaseResourceReferenceDt#getResource()}) or embedded resources (e.g. Bundle.entry.resource)
* </p>
*
* @param theResource The resource to visit
* @param theVisitor The visitor
* @param theElement The element to visit
* @param theVisitor The visitor
*/
public void visit(IBaseResource theResource, IModelVisitor2 theVisitor) {
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
visit(theResource, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
public void visit(IBase theElement, IModelVisitor2 theVisitor) {
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theElement.getClass());
if (def instanceof BaseRuntimeElementCompositeDefinition) {
BaseRuntimeElementCompositeDefinition<?> defComposite = (BaseRuntimeElementCompositeDefinition<?>) def;
visit(theElement, null, def, theVisitor, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
} else if (theElement instanceof IBaseExtension) {
theVisitor.acceptUndeclaredExtension((IBaseExtension<?, ?>) theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
} else {
theVisitor.acceptElement(theElement, Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
}
}
private void visit(Map<Object, Object> theStack, IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition,
@ -971,4 +1009,5 @@ public class FhirTerser {
});
}
}

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.util;
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR JPA Server
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%

View File

@ -25,7 +25,12 @@ import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.api.Constants;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -20,17 +20,26 @@ package ca.uhn.fhir.util;
* #L%
*/
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.primitive.StringDt;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@ -45,7 +54,7 @@ public class ParametersUtil {
}
public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer)t.getValue();
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer) t.getValue();
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
}
@ -53,33 +62,72 @@ public class ParametersUtil {
return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
}
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
public static List<IBase> getNamedParameters(FhirContext theCtx, IBaseResource theParameters, String theParameterName) {
Validate.notNull(theParameters, "theParameters must not be null");
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
return parameterReps
.stream()
.filter(param -> {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(param.getClass());
BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
List<IBase> nameValues = nameChild.getAccessor().getValues(param);
Optional<? extends IPrimitiveType<?>> nameValue = nameValues
.stream()
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<?>) t))
.findFirst();
if (!nameValue.isPresent() || !theParameterName.equals(nameValue.get().getValueAsString())) {
return false;
}
return true;
})
.collect(Collectors.toList());
}
public static Optional<IBase> getParameterPart(FhirContext theCtx, IBase theParameter, String theParameterName) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theParameter.getClass());
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("part");
List<IBase> parts = valueChild.getAccessor().getValues(theParameter);
for (IBase nextPart : parts) {
Optional<IPrimitiveType> name = theCtx.newTerser().getSingleValue(nextPart, "name", IPrimitiveType.class);
if (name.isPresent() && theParameterName.equals(name.get().getValueAsString())) {
return Optional.of(nextPart);
}
}
return Optional.empty();
}
public static Optional<IBase> getParameterPartValue(FhirContext theCtx, IBase theParameter, String theParameterName) {
Optional<IBase> part = getParameterPart(theCtx, theParameter, theParameterName);
if (part.isPresent()) {
return theCtx.newTerser().getSingleValue(part.get(), "value[x]", IBase.class);
} else {
return Optional.empty();
}
}
public static String getParameterPartValueAsString(FhirContext theCtx, IBase theParameter, String theParameterName) {
return getParameterPartValue(theCtx, theParameter, theParameterName).map(t -> (IPrimitiveType<?>) t).map(t -> t.getValueAsString()).orElse(null);
}
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
List<T> retVal = new ArrayList<>();
for (IBase nextParameter : parameterReps) {
List<IBase> namedParameters = getNamedParameters(theCtx, theParameters, theParameterName);
for (IBase nextParameter : namedParameters) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
BaseRuntimeChildDefinition nameChild = nextParameterDef.getChildByName("name");
List<IBase> nameValues = nameChild.getAccessor().getValues(nextParameter);
Optional<? extends IPrimitiveType<?>> nameValue = nameValues
.stream()
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<?>) t))
.findFirst();
if (!nameValue.isPresent() || !theParameterName.equals(nameValue.get().getValueAsString())) {
continue;
}
BaseRuntimeChildDefinition valueChild = nextParameterDef.getChildByName("value[x]");
List<IBase> valueValues = valueChild.getAccessor().getValues(nextParameter);
valueValues
.stream()
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t->((IPrimitiveType<?>) t))
.map(t -> ((IPrimitiveType<?>) t))
.map(theMapper)
.filter(t -> t != null)
.forEach(retVal::add);
@ -237,6 +285,13 @@ public class ParametersUtil {
addPart(theContext, theParameter, theName, value);
}
public static void addPartInteger(FhirContext theContext, IBase theParameter, String theName, Integer theInteger) {
IPrimitiveType<Integer> value = (IPrimitiveType<Integer>) theContext.getElementDefinition("integer").newInstance();
value.setValue(theInteger);
addPart(theContext, theParameter, theName, value);
}
public static void addPartString(FhirContext theContext, IBase theParameter, String theName, String theValue) {
IPrimitiveType<String> value = (IPrimitiveType<String>) theContext.getElementDefinition("string").newInstance();
value.setValue(theValue);
@ -267,7 +322,12 @@ public class ParametersUtil {
name.setValue(theName);
partChildElem.getChildByName("name").getMutator().addValue(part, name);
partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
if (theValue instanceof IBaseResource) {
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
} else {
partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
}
}
public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) {
@ -284,4 +344,62 @@ public class ParametersUtil {
partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
}
public static List<String> getNamedParameterPartAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
return extractNamedParameterPartsAsString(theCtx, theParameters, thePartName, theParameterName);
}
// TODO KHS need to consolidate duplicated functionality that came in from different branches
private static List<String> extractNamedParameterPartsAsString(FhirContext theCtx, IBaseParameters theParameters, String thePartName, String theParameterName) {
List<IBase> parameterReps = getParameterReps(theCtx, theParameters);
List<String> retVal = new ArrayList<>();
for (IBase nextParameter : parameterReps) {
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
Optional<? extends IPrimitiveType<?>> nameValue = getNameValue(nextParameter, nextParameterDef);
if (!nameValue.isPresent() || !thePartName.equals(nameValue.get().getValueAsString())) {
continue;
}
BaseRuntimeChildDefinition partChild = nextParameterDef.getChildByName("part");
List<IBase> partValues = partChild.getAccessor().getValues(nextParameter);
for (IBase partValue : partValues) {
BaseRuntimeElementCompositeDefinition<?> partParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(partValue.getClass());
Optional<? extends IPrimitiveType<?>> partNameValue = getNameValue(partValue, partParameterDef);
if (!partNameValue.isPresent() || !theParameterName.equals(partNameValue.get().getValueAsString())) {
continue;
}
BaseRuntimeChildDefinition valueChild = partParameterDef.getChildByName("value[x]");
List<IBase> valueValues = valueChild.getAccessor().getValues(partValue);
valueValues
.stream()
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<String>) t))
.map(t -> defaultIfBlank(t.getValueAsString(), null))
.filter(t -> t != null)
.forEach(retVal::add);
}
}
return retVal;
}
private static List<IBase> getParameterReps(FhirContext theCtx, IBaseParameters theParameters) {
Validate.notNull(theParameters, "theParameters must not be null");
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
return parameterChild.getAccessor().getValues(theParameters);
}
private static Optional<? extends IPrimitiveType<?>> getNameValue(IBase nextParameter, BaseRuntimeElementCompositeDefinition<?> theNextParameterDef) {
BaseRuntimeChildDefinition nameChild = theNextParameterDef.getChildByName("name");
List<IBase> nameValues = nameChild.getAccessor().getValues(nextParameter);
return nameValues
.stream()
.filter(t -> t instanceof IPrimitiveType<?>)
.map(t -> ((IPrimitiveType<?>) t))
.findFirst();
}
}

View File

@ -45,7 +45,7 @@ public class ResourceReferenceInfo {
public ResourceReferenceInfo(FhirContext theContext, IBaseResource theOwningResource, List<String> thePathToElement, IBaseReference theElement) {
myContext = theContext;
myOwningResource = theContext.getResourceDefinition(theOwningResource).getName();
myOwningResource = theContext.getResourceType(theOwningResource);
myResource = theElement;
if (thePathToElement != null && !thePathToElement.isEmpty()) {

View File

@ -1,8 +1,8 @@
package ca.uhn.fhir.jpa.model.util;
package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR Model
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2020 University Health Network
* %%
@ -24,7 +24,7 @@ import java.io.CharArrayWriter;
import java.text.Normalizer;
public class StringNormalizer {
public static String normalizeString(String theString) {
public static String normalizeStringForSearchIndexing(String theString) {
CharArrayWriter outBuffer = new CharArrayWriter(theString.length());
/*

View File

@ -31,16 +31,42 @@ import org.apache.commons.text.StringEscapeUtils;
import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.io.EscapingWriterFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.*;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLResolver;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.events.XMLEvent;
import java.io.*;
import java.util.*;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -1513,8 +1539,11 @@ public class XmlUtil {
VALID_ENTITY_NAMES = Collections.unmodifiableMap(validEntityNames);
}
/** Non-instantiable */
private XmlUtil() {}
/**
* Non-instantiable
*/
private XmlUtil() {
}
private static final class ExtendedEntityReplacingXmlResolver implements XMLResolver {
@Override
@ -1835,7 +1864,7 @@ public class XmlUtil {
}
public static Document parseDocument(String theInput) throws IOException, SAXException {
DocumentBuilder builder = null;
DocumentBuilder builder;
try {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
@ -1860,4 +1889,13 @@ public class XmlUtil {
InputSource src = new InputSource(new StringReader(theInput));
return builder.parse(src);
}
public static String encodeDocument(Element theElement) throws TransformerException {
TransformerFactory transFactory = TransformerFactory.newInstance();
Transformer transformer = transFactory.newTransformer();
StringWriter buffer = new StringWriter();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(new DOMSource(theElement), new StreamResult(buffer));
return buffer.toString();
}
}

View File

@ -23,6 +23,9 @@ import ca.uhn.fhir.rest.gclient.TokenClientParam;
* #L%
*/
/**
* An IBaseResource that has a FHIR version of DSTU3 or higher
*/
public interface IAnyResource extends IBaseResource {
/**

View File

@ -100,11 +100,17 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.unableToDeleteNotFound=Unable to fin
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
ca.uhn.fhir.rest.api.PatchTypeEnum.missingPatchContentType=Missing or invalid content type for PATCH operation
ca.uhn.fhir.rest.api.PatchTypeEnum.invalidPatchContentType=Invalid Content-Type for PATCH operation: {0}
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingMandatoryResource=Missing required resource in Bundle.entry[{1}].resource for operation {0}
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchContentType=Missing or invalid content type for PATCH operation
ca.uhn.fhir.jpa.dao.TransactionProcessor.missingPatchBody=Unable to determine PATCH body from request
ca.uhn.fhir.jpa.dao.TransactionProcessor.fhirPatchShouldNotUseBinaryResource=Binary PATCH detected with FHIR content type. FHIR Patch should use Parameters resource.
ca.uhn.fhir.jpa.patch.FhirPatch.invalidInsertIndex=Invalid insert index {0} for path {1} - Only have {2} existing entries
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveSourceIndex=Invalid move source index {0} for path {1} - Only have {2} existing entries
ca.uhn.fhir.jpa.patch.FhirPatch.invalidMoveDestinationIndex=Invalid move destination index {0} for path {1} - Only have {2} existing entries
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.externalReferenceNotAllowed=Resource contains external reference to URL "{0}" but this server is not configured to allow external references
ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
@ -139,7 +145,7 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateConceptMapUrl=Can
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can not create multiple ValueSet resources with ValueSet.url "{0}", already have one with resource ID: {1}
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.patch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
ca.uhn.fhir.jpa.graphql.JpaStorageServices.invalidGraphqlArgument=Unknown GraphQL argument "{0}". Value GraphQL argument for this type are: {1}
@ -165,3 +171,5 @@ ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl.cantRenameDefaultPartition=Can
ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}
ca.uhn.fhir.jpa.dao.HistoryBuilder.noSystemOrTypeHistoryForPartitionAwareServer=Type- and Server- level history operation not supported across partitions on partitioned server
ca.uhn.fhir.jpa.provider.DiffProvider.cantDiffDifferentTypes=Unable to diff two resources of different types

View File

@ -4,7 +4,8 @@ import org.junit.Test;
import java.time.LocalDate;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class RequestPartitionIdTest {

View File

@ -36,6 +36,11 @@
<artifactId>hapi-fhir-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hapi-fhir-server-empi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hapi-fhir-validation</artifactId>
@ -96,6 +101,11 @@
<artifactId>hapi-fhir-jpaserver-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hapi-fhir-jpaserver-empi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hapi-fhir-jpaserver-searchparam</artifactId>

View File

@ -129,7 +129,7 @@ public class ExampleDataUploader extends BaseCommand {
}
ourLog.info("Found example {} - {} - {} chars", nextEntry.getName(), parsed.getClass().getSimpleName(), exampleString.length());
if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) {
if (ctx.getResourceType(parsed).equals("Bundle")) {
BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry");
BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
@ -139,13 +139,13 @@ public class ExampleDataUploader extends BaseCommand {
continue;
}
for (IBase nextResource : resources) {
if (!ctx.getResourceDefinition(parsed).getName().equals("Bundle") && ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
if (!ctx.getResourceType(parsed).equals("Bundle") && ctx.getResourceType(parsed).equals("SearchParameter")) {
bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) nextResource);
}
}
}
} else {
if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
if (ctx.getResourceType(parsed).equals("SearchParameter")) {
continue;
}
bundle.addEntry().setRequest(new EntryRequest().setMethod(HTTPVerbEnum.POST)).setResource((IResource) parsed);
@ -205,7 +205,7 @@ public class ExampleDataUploader extends BaseCommand {
continue;
}
if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) {
if (ctx.getResourceType(parsed).equals("Bundle")) {
BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry");
BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
@ -227,7 +227,7 @@ public class ExampleDataUploader extends BaseCommand {
}
}
} else {
if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
if (ctx.getResourceType(parsed).equals("SearchParameter")) {
continue;
}
BundleEntryComponent entry = bundle.addEntry();
@ -289,7 +289,7 @@ public class ExampleDataUploader extends BaseCommand {
continue;
}
if (ctx.getResourceDefinition(parsed).getName().equals("Bundle")) {
if (ctx.getResourceType(parsed).equals("Bundle")) {
BaseRuntimeChildDefinition entryChildDef = ctx.getResourceDefinition(parsed).getChildByName("entry");
BaseRuntimeElementCompositeDefinition<?> entryDef = (BaseRuntimeElementCompositeDefinition<?>) entryChildDef.getChildByName("entry");
@ -311,7 +311,7 @@ public class ExampleDataUploader extends BaseCommand {
}
}
} else {
if (ctx.getResourceDefinition(parsed).getName().equals("SearchParameter")) {
if (ctx.getResourceType(parsed).equals("SearchParameter")) {
continue;
}
org.hl7.fhir.r4.model.Bundle.BundleEntryComponent entry = bundle.addEntry();
@ -663,7 +663,7 @@ public class ExampleDataUploader extends BaseCommand {
for (Iterator<IBaseResource> iter = resources.iterator(); iter.hasNext(); ) {
IBaseResource next = iter.next();
String nextType = ctx.getResourceDefinition(next).getName();
String nextType = ctx.getResourceType(next);
if (nextType.endsWith("Definition")) {
iter.remove();
} else if (nextType.contains("ValueSet")) {

View File

@ -36,7 +36,7 @@ public class LoadingValidationSupportDstu2 implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
String resName = myCtx.getResourceDefinition(theClass).getName();
String resName = myCtx.getResourceType(theClass);
ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri);
myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);

View File

@ -36,7 +36,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
String resName = myCtx.getResourceDefinition(theClass).getName();
String resName = myCtx.getResourceType(theClass);
ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri);
myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);

View File

@ -34,7 +34,7 @@ public class LoadingValidationSupportR4 implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
String resName = myCtx.getResourceDefinition(theClass).getName();
String resName = myCtx.getResourceType(theClass);
ourLog.info("Attempting to fetch {} at URL: {}", resName, theUri);
myCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);

View File

@ -26,7 +26,9 @@ import java.util.List;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class HapiFlywayMigrateDatabaseCommandTest {
@ -37,7 +39,6 @@ public class HapiFlywayMigrateDatabaseCommandTest {
System.setProperty("test", "true");
}
// TODO INTERMITTENT This just failed for me on CI with a BadSqlGrammarException
@Test
public void testMigrateFrom340() throws IOException, SQLException {

View File

@ -22,7 +22,11 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -31,7 +35,6 @@ import com.google.common.base.Charsets;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.dstu3.model.ConceptMap;
import org.hl7.fhir.dstu3.model.IdType;
import java.net.URI;
import java.net.URISyntaxException;

View File

@ -22,7 +22,11 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -31,7 +35,6 @@ import com.google.common.base.Charsets;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.IdType;
import java.net.URI;
import java.net.URISyntaxException;

View File

@ -22,10 +22,10 @@ package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.H2Dialect;
@ -70,7 +70,7 @@ public class CommonConfig {
/**
* The following bean configures the database connection. The 'url' property value of "jdbc:h2:file:target./jpaserver_h2_files" indicates that the server should save resources in a
* directory called "jpaserver_h2_files".
*
* <p>
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
*/
@Bean(destroyMethod = "close")
@ -105,7 +105,7 @@ public class CommonConfig {
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
extraProperties.put("hibernate.search.default.worker.execution", "async");
if (System.getProperty("lowmem") != null) {
extraProperties.put("hibernate.search.autoregister_listeners", "false");
}
@ -161,4 +161,9 @@ public class CommonConfig {
embeddedElasticSearch().stop();
}
@Bean
public PartitionSettings partitionSettings() {
return new PartitionSettings();
}
}

View File

@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.demo;
* #L%
*/
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;

View File

@ -23,10 +23,10 @@ package ca.uhn.fhir.jpa.demo;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.BaseConfig;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
@ -36,7 +36,6 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -45,6 +44,8 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
@ -176,6 +177,9 @@ public class JpaServerDemo extends RestfulServer {
IInterceptorBroadcaster interceptorBroadcaster = myAppCtx.getBean(IInterceptorBroadcaster.class);
CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, daoRegistry, interceptorBroadcaster);
getInterceptorService().registerInterceptor(cascadingDeleteInterceptor);
getInterceptorService().registerInterceptor(new ResponseHighlighterInterceptor());
}
}

View File

@ -1,5 +1,15 @@
package ca.uhn.fhir.okhttp.client;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
import okhttp3.Call;
import okhttp3.Call.Factory;
import okhttp3.Request;
import okhttp3.RequestBody;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@ -25,16 +35,6 @@ import java.util.Map;
* #L%
*/
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.BaseHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
import ca.uhn.fhir.util.StopWatch;
import okhttp3.Call;
import okhttp3.Call.Factory;
import okhttp3.Request;
import okhttp3.RequestBody;
/**
* Adapter for building an OkHttp-specific request.
*

View File

@ -361,7 +361,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private String toResourceName(Class<? extends IBaseResource> theType) {
return myContext.getResourceDefinition(theType).getName();
return myContext.getResourceType(theType);
}
@Override
@ -787,7 +787,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) {
Validate.notNull(theResourceType, "theResourceType can not be null");
myConditional = true;
myResourceType = myContext.getResourceDefinition(theResourceType).getName();
myResourceType = myContext.getResourceType(theResourceType);
return this;
}
@ -904,7 +904,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
String resourceName;
String id;
if (myType != null) {
resourceName = myContext.getResourceDefinition(myType).getName();
resourceName = myContext.getResourceType(myType);
id = null;
} else if (myId != null) {
resourceName = myId.getResourceType();
@ -931,6 +931,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@Override
public IHistoryUntyped onInstance(String theId) {
Validate.notBlank(theId, "theId must not be null or blank");
IIdType id = myContext.getVersion().newIdType();
id.setValue(theId);
return onInstance(id);
}
@Override
public IHistoryUntyped onServer() {
return this;
@ -1274,7 +1282,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
String id;
String version;
if (myType != null) {
resourceName = myContext.getResourceDefinition(myType).getName();
resourceName = myContext.getResourceType(myType);
id = null;
version = null;
} else if (myId != null) {
@ -1297,7 +1305,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
return retVal;
}
IClientResponseHandler handler = new ResourceOrBinaryResponseHandler()
.setPreferResponseTypes(getPreferResponseTypes(myType));
.setPreferResponseTypes(getPreferResponseTypes(myType));
if (myReturnMethodOutcome) {
handler = new MethodOutcomeResponseHandler(handler);
@ -1339,6 +1347,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@Override
public IOperationUnnamed onInstance(String theId) {
Validate.notBlank(theId, "theId must not be null or blank");
IIdType id = myContext.getVersion().newIdType();
id.setValue(theId);
return onInstance(id);
}
@Override
public IOperationUnnamed onInstanceVersion(IIdType theId) {
myId = theId;
@ -1478,7 +1494,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private final class MethodOutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
private final class MethodOutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
private final IClientResponseHandler<? extends IBaseResource> myWrap;
private MethodOutcomeResponseHandler(IClientResponseHandler<? extends IBaseResource> theWrap) {
@ -1543,7 +1559,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IPatchWithQuery conditional(Class<? extends IBaseResource> theClass) {
Validate.notNull(theClass, "theClass must not be null");
String resourceType = myContext.getResourceDefinition(theClass).getName();
String resourceType = myContext.getResourceType(theClass);
return conditional(resourceType);
}
@ -1617,14 +1633,23 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@Override
public IPatchWithBody withFhirPatch(IBaseParameters thePatchBody) {
Validate.notNull(thePatchBody, "thePatchBody must not be null");
myPatchType = PatchTypeEnum.FHIR_PATCH_JSON;
myPatchBody = myContext.newJsonParser().encodeResourceToString(thePatchBody);
return this;
}
@Override
public IPatchExecutable withId(IIdType theId) {
if (theId == null) {
throw new NullPointerException("theId can not be null");
}
if (theId.hasIdPart() == false) {
throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue());
}
Validate.notBlank(theId.getIdPart(), "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s", UrlUtil.sanitizeUrlPart(theId.getValue()));
Validate.notBlank(theId.getResourceType(), "theId must not be blank and must contain a resource type and ID (e.g. \"Patient/123\"), found: %s", UrlUtil.sanitizeUrlPart(theId.getValue()));
myId = theId;
return this;
}
@ -1634,11 +1659,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (theId == null) {
throw new NullPointerException("theId can not be null");
}
if (isBlank(theId)) {
throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId);
}
myId = new IdDt(theId);
return this;
return withId(new IdDt(theId));
}
}

View File

@ -59,7 +59,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
throw new ConfigurationException("Unable to determine resource type for method: " + theMethod);
}
myResourceName = theContext.getResourceDefinition(myResourceType).getName();
myResourceName = theContext.getResourceType(myResourceType);
if (resourceParameter == null) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a resource parameter annotated with @" + ResourceParam.class.getSimpleName());

View File

@ -99,7 +99,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
// If we're returning an abstract type, that's ok
} else {
myResourceType = (Class<? extends IResource>) theReturnResourceType;
myResourceName = theContext.getResourceDefinition(myResourceType).getName();
myResourceName = theContext.getResourceType(myResourceType);
}
}
}

View File

@ -53,7 +53,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
@Override
protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IBaseResource theResource) {
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(getContext().getResourceDefinition(theResource).getName());
urlExtension.append(getContext().getResourceType(theResource));
return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString());
}

View File

@ -64,7 +64,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
if (type != IBaseResource.class && type != IResource.class) {
myResourceName = theContext.getResourceDefinition(type).getName();
myResourceName = theContext.getResourceType(type);
} else {
myResourceName = null;
}

View File

@ -20,9 +20,6 @@ package ca.uhn.fhir.rest.client.method;
* #L%
*/
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
@ -30,6 +27,9 @@ import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.UrlSourceEnum;
import ca.uhn.fhir.rest.client.impl.BaseHttpClientInvocation;
import java.util.List;
import java.util.Map;
public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
private final String myUrl;

View File

@ -160,7 +160,7 @@ public class MethodUtil {
public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IBaseResource theResource,
String theResourceBody, Map<String, List<String>> theMatchParams) {
String resourceType = theContext.getResourceDefinition(theResource).getName();
String resourceType = theContext.getResourceType(theResource);
StringBuilder b = createUrl(resourceType, theMatchParams);
@ -188,7 +188,7 @@ public class MethodUtil {
public static HttpPutClientInvocation createUpdateInvocation(IBaseResource theResource, String theResourceBody,
IIdType theId, FhirContext theContext) {
String resourceName = theContext.getResourceDefinition(theResource).getName();
String resourceName = theContext.getResourceType(theResource);
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(resourceName);
urlBuilder.append('/');

View File

@ -83,10 +83,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myName = theOperationName;
if (theReturnTypeFromRp != null) {
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
setResourceName(theContext.getResourceType(theReturnTypeFromRp));
} else {
if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
setResourceName(theContext.getResourceDefinition(theOperationType).getName());
setResourceName(theContext.getResourceType(theOperationType));
} else {
setResourceName(null);
}

View File

@ -83,7 +83,7 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes
@Override
protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IBaseResource theResource) {
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(getContext().getResourceDefinition(theResource).getName());
urlExtension.append(getContext().getResourceType(theResource));
return new HttpPostClientInvocation(getContext(), theResource, urlExtension.toString());
}

View File

@ -29,7 +29,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
@ -72,7 +71,7 @@ public class ValidateMethodBindingDstu2Plus extends OperationMethodBinding {
IBaseParameters parameters = (IBaseParameters) theContext.getResourceDefinition("Parameters").newInstance();
ParametersUtil.addParameterToParameters(theContext, parameters, "resource", theResource);
String resourceName = theContext.getResourceDefinition(theResource).getName();
String resourceName = theContext.getResourceType(theResource);
String resourceId = theResource.getIdElement().getIdPart();
BaseHttpClientInvocation retVal = createOperationInvocation(theContext, resourceName, resourceId, null,Constants.EXTOP_VALIDATE, parameters, false);

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1850
title: "A new operation has been added to the JPA server called `$diff`. This operation can generate a FHIR Patch diff showing
the changes between two versions of a resource, or even two separate resources. See [Diff](/hapi-fhir/docs/server_jpa/diff.html)
for more information."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1850
title: "The [FHIR Patch](https://www.hl7.org/fhir/fhirpatch.html) format is now supported for patching resources, in addition
to the previously supported JSON Patch and XML Patch."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1850
title: When serializing a resource, the JSON Parser will now ignore any extensions that are present on an
element if they do not have a URL or any children populated.

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1851
title: "In HAPI FHIR 5.0.0, partitioned JPA servers were introduced. The documentation claimed that an interceptor implementing
the STORAGE_PARTITION_IDENTIFY_READ was optional, but the server incorrectly made this mandatory. This has been
corrected."

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1853
title: "The `@Interceptor` annotation was not marked as inheritable, meaning that the order attribute was lost when
using Spring proxies. Thanks to Tue Toft Nørgård for the pull request!"

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 1854
title: "When using a SearchParameter with uniqueness enabled, the `$expunge` operation sometimes failed to expunge resources with
a database constraint error. This has been fixed."

View File

@ -0,0 +1,11 @@
---
type: remove
issue: 1855
title: "A very old feature that is not believed to be used anywhere has been removed: The ServerProfileProvider is
a special resource provider that was automatically registered to HAPI FHIR REST servers, and served up StructureDefinitions
that were registered to the FhirContext. Registering custom StructureDefinitions against the FhirContext for exposure through
the REST API (as what was then the /Profile endpoint) was planned to be a common feature during the DSTU1 lifecycle but
did not turn out to be a useful approach. This feature was mostly forgotten about until the logic for selecting resource
provider handler methods was revamped and the old mechanism suddenly became the default resource provider for StructureDefinition
resources in the JPA server. We don't expect any negative impact by this change, please post in our mailing list if you
disagree."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1856
title: "The subscription delivery queue in the JPA server was erroniously keeping both a copy of the serialized and the
deserialized payload in memory for each entry in the queue, doubling the memory requirements. This also caused failures
when delivering XML payloads in some configurations. This has been corrected."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 1863
title: "Cascade deletes were failing in cases where the resource being deleted had more than 600 conflicts due to a hard-coded limit
on the number of conflicts that the CascadingDeleteInterceptor was allowed to process at a time. This hard-coded limit has been
replaced with an optional configuration parameter."

View File

@ -0,0 +1,8 @@
---
- item:
issue: "1857"
type: "change"
title: "**Breaking Change**:
The method `FhirContext#getResourceNames()` has been renamed to `FhirContext#getResourceTypes()`. HAPI currently
goes back and forth between the two, but is consolidating on `Types`.
"

View File

@ -209,6 +209,22 @@ FHIR also specifies a type of update called "conditional updates", where instead
**See Also:** See the description of [Update ETags](#update_etags) below for information on specifying a matching version in the client request.
# Patch - Instance
The PATCH operation can be used to modify a resource in place by supplying a delta
The following example shows how to perform a patch using a [FHIR Patch](http://hl7.org/fhir/fhirpatch.html)
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|patchFhir}}
```
The following example shows how to perform a patch using a [JSON Patch](https://tools.ietf.org/html/rfc6902.)
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|patchJson}}
```
# History - Server/Type/Instance
To retrieve the version history of all resources, or all resources of a given type, or of a specific instance of a resource, you call the [`history()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#history()) method.

View File

@ -46,6 +46,12 @@ page.server_jpa.configuration=Configuration
page.server_jpa.search=Search
page.server_jpa.performance=Performance
page.server_jpa.upgrading=Upgrade Guide
page.server_jpa.diff=Diff Operation
section.server_jpa_empi.title=JPA Server: EMPI
page.server_jpa_empi.empi=Enterprise Master Patient Index
page.server_jpa_empi.empi_operations=EMPI Operations
page.server_jpa_empi.empi_settings=Enabling EMPI in HAPI FHIR
section.server_jpa_partitioning.title=JPA Server: Partitioning and Multitenancy
page.server_jpa_partitioning.partitioning=Partitioning and Multitenancy

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,88 @@
# Diff Operation
The `$diff` operation can be used to generate a differential between two versions of a resource, or even two different resources of the same type.
Differentials generated by this operation are in [FHIR Patch](https://www.hl7.org/fhir/fhirpatch.html) format.
In generated differentials, where a value has changed (i.e. a **replace** operation), an additional part value will be present on the given operation called `previousValue`. This part shows the value as it was in the *from* version of the resource.
# Diff Instance
When the $diff operation is invoked at the instance level (meaning it is invoked on a specific resource ID), it will compare two versions of the given resource.
## Parameters
* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_VERSION_PARAMETER}]]=[versionId]`: (*optional*) If specified, compare using this version as the source. If not specified, the immediately previous version will be compared.
* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default.
To invoke:
```http
GET http://fhir.example.com/baseR4/Patient/123/$diff
```
The server will produce a response resembling the following:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "operation",
"part": [ {
"name": "type",
"valueCode": "replace"
}, {
"name": "path",
"valueString": "Patient.name.family"
}, {
"name": "previousValue",
"valueId": "Smyth"
}, {
"name": "value",
"valueId": "SmithB"
} ]
} ]
}
```
# Diff Instance
When the $diff operation is invoked at the instance level (meaning it is invoked on a specific resource ID), it will compare two versions of the given resource.
## Parameters
* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_PARAMETER}]]=[reference]`: Specifies the source of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`.
* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_TO_PARAMETER}]]=[reference]`: Specifies the target of the comparison. The value must include a resource type and a resource ID, and can optionally include a version, e.g. `Patient/123` or `Patient/123/_history/2`.
* `[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_INCLUDE_META_PARAMETER}]]=true`: (*optional*) If specified, changes to Resource.meta will be included in the diff. This element is omitted by default.
To invoke:
```http
GET http://fhir.example.com/baseR4/$diff?[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_FROM_PARAMETER}]]=Patient/1&[[${T(ca.uhn.fhir.rest.server.provider.ProviderConstants).DIFF_TO_PARAMETER}]]=Patient/2
```
The server will produce a response resembling the following:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "operation",
"part": [ {
"name": "type",
"valueCode": "replace"
}, {
"name": "path",
"valueString": "Patient.id"
}, {
"name": "previousValue",
"valueId": "1"
}, {
"name": "value",
"valueId": "2"
} ]
} ]
}
```

View File

@ -0,0 +1,158 @@
# Enterprise Master Person Index (EMPI)
HAPI FHIR 5.0.0 introduced preliminary support for **EMPI**.
An EMPI allows for links to be created and maintained between different Patient and/or Practitioner resources. These links are used to indicate the fact that different Patient/Practitioner resources are known or believed to refer to the same actual (real world) person.
These links may be created and updated using different combinations of automatic linking as well as manual linking.
Note: The following sections describe linking between Patient and Person resources. The same information applies for linking between Practitioner and Person, but for readability it is not repeated.
## Working Example
The [JPA Server Starter](/hapi-fhir/docs/server_jpa/get_started.html) project contains a complete working example of the HAPI EMPI feature and documentation about how to enable and configure it. You may wish to browse its source to see how this works.
## Person linking in FHIR
Because HAPI EMPI is implemented on the HAPI JPA Server, it uses the FHIR model to represent roles and links. The following illustration shows an example of how these links work.
<a href="/hapi-fhir/docs/images/empi-links.svg"><img src="/hapi-fhir/docs/images/empi-links.svg" alt="EMPI links" style="margin-left: 15px; margin-bottom: 15px; width: 500px;" /></a>
There are several resources that are used:
* Patient - Represents the record of a person who receives healthcare services
* Person - Represents a master record with links to one or more Patient and/or Practitioner resources that belong to the same person
# Automatic Linking
With EMPI enabled, the basic default behavior of the EMPI is simply to create a new Person record for every Patient that is created such that there is a 1:1 relationship between them. Any relinking is then expected to be done manually (i.e. via the forthcoming empi operations).
In a typical configuration it is often desirable to have links be created automatically using matching rules. For example, you might decide that if a Patient shares the same name, gender, and date of birth as another Patient, you have at least a little confidence that they are the same Person.
This automatic linking is done via configurable matching rules that create a links between Patients and Persons. Based on the strength of the match configured in these rules, the link will be set to either POSSIBLE_MATCH or MATCHED.
## Design Principles
Below are some simplifying principles HAPI EMPI enforces to reduce complexity and ensure data integrity.
1. When EMPI is enabled on a HAPI FHIR server, any Person resource in the repository that has the "hapi-empi" tag is considered read-only via the FHIR endpoint. These Person resources are managed exclusively by HAPI EMPI. Users can only directly change them via special empi operations. In most cases, users will indirectly change them by creating and updating Patient and Practitioner ("Patient") resources. For the rest of this document, assume "Person" refers to a "hapi-empi" tagged Person resource.
1. Every Patient in the system has a MATCH link to at most one Person resource.
1. Every Patient resource in the system has a MATCH link to a Person resource unless that Patient has the "no-empi" tag or it has POSSIBLE_MATCH links pending review.
1. The HAPI EMPI rules define a single identifier system that holds the external enterprise id ("EID"). If a Patient has an external EID, then the Person it links to always has the same EID. If a patient has no EID when it arrives, the person created from this patient is given an internal EID.
1. A Person can have both an internal EID(auto-created by HAPI), and an external EID (provided by an external system).
1. Two different Person resources cannot have the same EID.
1. Patient resources are only ever compared to Person resources via this EID. For all other matches, Patient resources are only ever compared to Patient resources and Practitioner resources are only ever compared to Practitioner resources.
## Links
1. HAPI EMPI manages empi-link records ("links") that link a Patient resource to a Person resource. When these are created/updated by matching rules, the links are marked as AUTO. When these links are changed manually, they are marked as MANUAL.
1. Once a link has been manually assigned as NO_MATCH or MATCHED, the system will not change it.
1. When a new Patient resource is created/updated then it is compared to all other Patient resources in the repository. The outcome of each of these comparisons is either NO_MATCH, POSSIBLE_MATCH or MATCHED.
1. Whenever a MATCHED link is established between a Patient resource and a Person resource, that Patient is always added to that Person resource links. All MATCHED links have corresponding Person resource links and all Person resource links have corresponding MATCHED empi-link records. You can think of the fields of the empi-link records as extra meta-data associated with each Person.link.target.
### Possible rule match outcomes:
When a new Patient resource is compared with all other resources of that type in the repository, there are four possible cases:
* CASE 1: No MATCHED and no POSSIBLE_MATCHED outcomes -> a new Person resource is created and linked to that Patient as MATCHED. All fields are copied from the Patient to the Person. If the incoming resource has an EID, it is copied to the Person. Otherwise a new UUID is created and used as the internal EID.
* CASE 2: All of the MATCHED Patient resources are already linked to the same Person -> a new Link is created between the new Patient and that Person and is set to MATCHED.
* CASE 3: The MATCHED Patient resources link to more than one Person -> Mark all links as POSSIBLE_MATCHED. All other Person resources are marked as POSSIBLE_DUPLICATE of this first Person. These duplicates are manually reviewed later and either merged or marked as NO_MATCH and the system will no longer consider them as a POSSIBLE_DUPLICATE going forward. POSSIBLE_DUPLICATE is the only link type that can have a Person as both the source and target of the link.
* CASE 4: Only POSSIBLE_MATCH outcomes -> In this case, empi-link records are created with POSSIBLE_MATCH outcome and await manual assignment to either NO_MATCH or MATCHED. Person resources are not changed.
# Rules
HAPI EMPI rules are managed via a single json document. This document contains a version. empi-links derived from these rules are marked with this version. The following configuration is stored in the rules:
* **resourceSearchParams**: These define fields which must have at least one exact match before two resources are considered for matching. This is like a list of "pre-searches" that find potential candidates for matches, to avoid the expensive operation of running a match score calculation on all resources in the system. E.g. you may only wish to consider matching two Patients if they either share at least one identifier in common or have the same birthday.
```json
[ {
"resourceType" : "Patient",
"searchParam" : "birthdate"
}, {
"resourceType" : "Patient",
"searchParam" : "identifier"
} ]
```
* **filterSearchParams** When searching for match candidates, only resources that match this filter are considered. E.g. you may wish to only search for Patients for which active=true.
```json
[ {
"resourceType" : "Patient",
"searchParam" : "active",
"fixedValue" : "true"
} ]
```
* **matchFields** Once the match candidates have been found, they are then each assigned a match vector that marks which fields match. The match vector is determined by a list of matchFields. Each matchField defines a name, distance metric, a success threshold, a resource type, and resource path to check. For example:
```json
{
"name" : "given-name-cosine",
"resourceType" : "Patient",
"resourcePath" : "name.given",
"metric" : "COSINE",
"matchThreshold" : 0.8
}
```
Note that in all the above json, valid options for `resourceType` are `Patient`, `Practitioner`, and `All`. Use `All` if the criteria is identical across both resource types, and you would like to apply the pre-search to both practitioners and patients.
The following metrics are currently supported:
* JARO_WINKLER
* COSINE
* JACCARD
* NORMALIZED_LEVENSCHTEIN
* SORENSEN_DICE
* STANDARD_NAME_ANY_ORDER
* EXACT_NAME_ANY_ORDER
* STANDARD_NAME_FIRST_AND_LAST
* EXACT_NAME_FIRST_AND_LAST
See [java-string-similarity](https://github.com/tdebatty/java-string-similarity) for a description of the first five metrics. For the last four, STANDARd means ignore case and accents whereas EXACT must match casing and accents exactly. Name any order matches first and last names irrespective of order, whereas FIRST_AND_LAST metrics require the name match to be in order.
* **matchResultMap** A map which converts combinations of successful matchFields into an EMPI Match Result score for overall matching of a given pair of resources.
```json
"matchResultMap" : {
"given-name-cosine" : "POSSIBLE_MATCH",
"given-name-jaro, last-name-jaro" : "MATCH"
}
```
* **eidSystem**: The external EID system that the HAPI EMPI system should expect to see on incoming Patient resources. Must be a valid URI.
# Enterprise Identifiers
An Enterprise Identifier(EID) is a unique identifier that can be attached to Patients or Practitioners. Each implementation is expected to use exactly one EID system for incoming resources,
defined in the mentioned `empi-rules.json` file. If a Patient or Practitioner with a valid EID is added to the system, that EID will be copied over to the Person that was matched. In the case that
the incoming Patient or Practitioner had no EID assigned, an internal EID will be created for it. There are thus two classes of EID. Internal EIDs, created by HAPI-EMPI, and External EIDs, provided
by the install.
There are many edge cases for determining what will happen in merge and update scenarios, which will be provided in future documentation.
# HAPI EMPI Technical Details
When EMPI is enabled, the HAPI FHIR JPA Server does the following things on startup:
1. HAPI EMPI stores the extra link details in a table called `MPI_LINK`.
1. Each record in an `MPI_LINK` table corresponds to a `link.target` entry on a Person resource. HAPI EMPI uses the following convention for the Person.link.assurance level:
1. Level 1: not used
1. Level 2: POSSIBLE_MATCH
1. Level 3: AUTO MATCHED
1. Level 4: MANUAL MATCHED
1. It enables the MESSAGE subscription type and starts up the internal subscription engine.
1. It creates two MESSAGE subscriptions, called 'empi-patient' and 'empi-practitioner' that match all incoming Patient and Practitioner resources and send them to an internal queue called "empi". The JPA Server listens to this queue and links incoming resources to Persons.
1. It registers the `Patient/$match` operation. See [$match](https://www.hl7.org/fhir/operation-patient-match.html) for a description of this operation.
1. It registers a new dao interceptor that restricts access to EMPI managed Person records.

View File

@ -0,0 +1,362 @@
# EMPI Operations
Several operations exist that can be used to manage EMPI links. These operations are supplied by a [plain provider](/docs/server_plain/resource_providers.html#plain-providers) called [EmpiProvider](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/provider/EmpiProviderR4.html).
## Query links
Ue the `$empi-query-links` operation to view empi links. The results returned are based on the parameters provided. All parameters are optional. This operation takes the following parameters:
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Cardinality</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>personId</td>
<td>String</td>
<td>0..1</td>
<td>
The id of the Person resource.
</td>
</tr>
<tr>
<td>targetId</td>
<td>String</td>
<td>0..1</td>
<td>
The id of the Patient or Practitioner resource.
</td>
</tr>
<tr>
<td>matchResult</td>
<td>String</td>
<td>0..1</td>
<td>
MATCH, POSSIBLE_MATCH or NO_MATCH.
</td>
</tr>
<tr>
<td>linkSource</td>
<td>String</td>
<td>0..1</td>
<td>
AUTO, MANUAL.
</td>
</tr>
</tbody>
</table>
### Example
Use an HTTP GET like `http://example.com/$empi-query-link?matchResult=POSSIBLE_MATCH` or an HTTP POST to the following URL to invoke this operation:
```url
http://example.com/$empi-query-link
```
The following request body could be used to find all POSSIBLE_MATCH links in the system:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "matchResult",
"valueString": "POSSIBLE_MATCH"
} ]
}
```
This operation returns a `Parameters` resource that looks like the following:
```json
<Parameters xmlns="http://hl7.org/fhir">
<parameter>
<name value="link"/>
<part>
<name value="personId"/>
<valueString value="Person/123"/>
</part>
<part>
<name value="targetId"/>
<valueString value="Patient/456"/>
</part>
<part>
<name value="matchResult"/>
<valueString value="MATCH"/>
</part>
<part>
<name value="linkSource"/>
<valueString value="AUTO"/>
</part>
</parameter>
</Parameters>
```
## Querying links via the Person resource
Alternatively, you can query Empi links by querying Person resources directly. Empi represents links in Person resources using the following mapping:
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>EMPI matchResult</th>
<th>EMPI linkSource</th>
<th>Person link.assurance</th>
</tr>
</thead>
<tbody>
<tr>
<td>NO_MATCH</td>
<td>MANUAL</td>
<td>No link present</td>
</tr>
<tr>
<td>POSSIBLE_MATCH</td>
<td>AUTO</td>
<td>level2</td>
</tr>
<tr>
<td>MATCH</td>
<td>AUTO</td>
<td>level3</td>
</tr>
<tr>
<td>MATCH</td>
<td>MANUAL</td>
<td>level4</td>
</tr>
</tbody>
</table>
For example, you can use the following HTTP GET to find all Person resources that have POSSIBLE_MATCH links:
```
http://example.com/Person?assurance=level2
```
## Query Duplicate Persons
Use the `$empi-duplicate-persons` operation to request a list of duplicate persons. This operation takes no parameters
### Example
Use an HTTP GET to the following URL to invoke this operation:
```url
http://example.com/$empi-duplicate-persons
```
This operation returns `Parameters` similar to `$empi-query-links`:
```json
<Parameters xmlns="http://hl7.org/fhir">
<parameter>
<name value="link"/>
<part>
<name value="personId"/>
<valueString value="Person/123"/>
</part>
<part>
<name value="targetId"/>
<valueString value="Person/789"/>
</part>
</parameter>
</Parameters>
```
## Update Link
Use the `$empi-update-link` operation to change the `matchResult` update of an empi link. This operation takes the following parameters:
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Cardinality</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>personId</td>
<td>String</td>
<td>1..1</td>
<td>
The id of the Person resource.
</td>
</tr>
<tr>
<td>targetId</td>
<td>String</td>
<td>1..1</td>
<td>
The id of the Patient or Practitioner resource.
</td>
</tr>
<tr>
<td>matchResult</td>
<td>String</td>
<td>1..1</td>
<td>
Must be either MATCH or NO_MATCH.
</td>
</tr>
</tbody>
</table>
Empi links updated in this way will automatically have their `linkSource` set to `MANUAL`.
### Example
Use an HTTP POST to the following URL to invoke this operation:
```url
http://example.com/$empi-update-link
```
The following request body could be used:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "personId",
"valueString": "Person/123"
}, {
"name": "targetId",
"valueString": "Patient/456"
}, {
"name": "matchResult",
"valueString": "MATCH"
} ]
}
```
The operation returns the updated `Person` resource. Note that this is the only way to modify EMPI-managed `Person` resources.
## Merge Persons
The `$empi-merge-persons` operation can be used to merge one Person resource with another. When doing this, you will need to decide which resource to delete and which one to keep. Data from the personToKeep will be given precedence over data in the personToDelete. This operation takes the following parameters:
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Cardinality</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>personIdToDelete</td>
<td>String</td>
<td>1..1</td>
<td>
The id of the Person resource to merge data from. This resource will be deleted after the merge.
</td>
</tr>
<tr>
<td>personIdToKeep</td>
<td>String</td>
<td>1..1</td>
<td>
The id of the Person to merge data into.
</td>
</tr>
</tbody>
</table>
### Example
Use an HTTP POST to the following URL to invoke this operation:
```url
http://example.com/$empi-merge-persons
```
The following request body could be used:
```json
{
"resourceType": "Parameters",
"parameter": [ {
"name": "personIdToDelete",
"valueString": "Person/123"
}, {
"name": "personIdToKeep",
"valueString": "Patient/128"
} ]
}
```
This operation returns the merged Person resource.
# Querying The EMPI
When EMPI is enabled, the [$match operation](http://hl7.org/fhir/patient-operation-match.html) will be enabled on the JPA Server.
This operation allows a Patient resource to be submitted to the endpoint, and the system will attempt to find and return any Patient resources that match it according to the matching rules.
For example, the following request may be submitted:
```http
POST /Patient/$match
Content-Type: application/fhir+json; charset=UTF-8
{
"resourceType":"Parameters",
"parameter": [
{
"name":"resource",
"resource": {
"resourceType":"Patient",
"name": [
{ "family":"foo" }
]
}
}
]
}
```
This might result in a response such as the following:
```json
{
"resourceType": "Bundle",
"id": "0e712adc-6979-4875-bbe9-70b883a955b8",
"meta": {
"lastUpdated": "2019-06-06T22:46:43.809+03:30"
},
"type": "searchset",
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "3",
"meta": {
"versionId": "1",
"lastUpdated": "2019-06-06T22:46:43.339+03:30"
},
"name": [
{
"family": "foo",
"given": [
"bar"
]
}
],
"birthDate": "2000-01-01"
}
}
]
}
```

View File

@ -0,0 +1,11 @@
# Enabling EMPI in HAPI FHIR
Follow these steps to enable EMPI on the server:
The [EmpiSettings](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html) bean contains configuration settings related to EMPI within the server. To enable Empi, the [setEnabled(boolean)](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setEnabled(boolean)) property should be enabled.
The following settings are enabled by default:
* **Prevent EID Updates** ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setPreventEidUpdates(boolean))): If this is enabled, then once an EID is set on a resource, it cannot be changed. If disabled, patients may have their EID updated.
* **Prevent multiple EIDs**: ([JavaDoc](/hapi-fhir/apidocs/hapi-fhir-server-empi/ca/uhn/fhir/empi/rules/config/EmpiSettings.html#setPreventMultipleEids(boolean))): If this is enabled, then a resource cannot have more than one EID, and incoming resources that break this rule will be rejected.

View File

@ -31,6 +31,11 @@
<artifactId>hapi-fhir-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server-empi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client</artifactId>
@ -101,6 +106,11 @@
<artifactId>hapi-fhir-jpaserver-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-empi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-api</artifactId>

View File

@ -83,6 +83,7 @@ public class DaoConfig {
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
private static final int DEFAULT_MAXIMUM_DELETE_CONFLICT_COUNT = 60;
/**
* Child Configurations
@ -153,6 +154,10 @@ public class DaoConfig {
private ClientIdStrategyEnum myResourceClientIdStrategy = ClientIdStrategyEnum.ALPHANUMERIC;
private boolean myFilterParameterEnabled = false;
private StoreMetaSourceInformationEnum myStoreMetaSourceInformation = StoreMetaSourceInformationEnum.SOURCE_URI_AND_REQUEST_ID;
/**
* update setter javadoc if default changes
*/
private Integer myMaximumDeleteConflictQueryCount = DEFAULT_MAXIMUM_DELETE_CONFLICT_COUNT;
/**
* Do not change default of {@code true}!
*
@ -2021,4 +2026,33 @@ public class DaoConfig {
*/
ANY
}
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public Integer getMaximumDeleteConflictQueryCount() {
return myMaximumDeleteConflictQueryCount;
}
/**
* <p>
* This determines the maximum number of conflicts that should be fetched and handled while retrying a delete of a resource.
* </p>
* <p>
* The default value for this setting is {@code 60}.
* </p>
*
* @since 5.1.0
*/
public void setMaximumDeleteConflictQueryCount(Integer theMaximumDeleteConflictQueryCount) {
myMaximumDeleteConflictQueryCount = theMaximumDeleteConflictQueryCount;
}
}

View File

@ -23,8 +23,6 @@ package ca.uhn.fhir.jpa.api.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.Validate;
@ -35,7 +33,14 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.annotation.Nullable;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
@ -76,7 +81,6 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
myAppCtx = theApplicationContext;
}
public IFhirSystemDao getSystemDao() {
IFhirSystemDao retVal = mySystemDao;
if (retVal == null) {
@ -118,7 +122,7 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
@Nullable
public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoOrNull(Class<T> theResourceType) {
String resourceName = myContext.getResourceDefinition(theResourceType).getName();
String resourceName = myContext.getResourceType(theResourceType);
try {
return (IFhirResourceDao<T>) getResourceDao(resourceName);
} catch (InvalidRequestException e) {
@ -184,10 +188,10 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
List<String> supportedResourceNames = myResourceNameToResourceDao
.keySet()
.stream()
.map(t -> myContext.getResourceDefinition(t).getName())
.map(t -> myContext.getResourceType(t))
.sorted()
.collect(Collectors.toList());
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceDefinition(theClass).getName() + " - Can handle: " + supportedResourceNames);
throw new InvalidRequestException("Unable to process request, this server does not know how to handle resources of type " + myContext.getResourceType(theClass) + " - Can handle: " + supportedResourceNames);
}
return retVal;
}

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -153,7 +154,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
*/
<MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails);
DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequestDetails);
DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequestDetails);
/**
* Read a resource - Note that this variant of the method does not take in a {@link RequestDetails} and

View File

@ -1,5 +1,12 @@
package ca.uhn.fhir.jpa.api.dao;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.http.HttpServletRequest;
/*
@ -21,11 +28,6 @@ import javax.servlet.http.HttpServletRequest;
* limitations under the License.
* #L%
*/
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateRangeParam;
public interface IFhirResourceDaoEncounter<T extends IBaseResource> extends IFhirResourceDao<T> {

View File

@ -19,9 +19,11 @@ package ca.uhn.fhir.jpa.api.dao;
* limitations under the License.
* #L%
*/
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
public interface IFhirResourceDaoValueSet<T extends IBaseResource, CD, CC> extends IFhirResourceDao<T> {

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.jpa.api.dao;
* #L%
*/
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ResourceMetadataKeySupportingAnyResource;
import org.hl7.fhir.instance.model.api.IAnyResource;

View File

@ -20,11 +20,11 @@ package ca.uhn.fhir.jpa.api.model;
* #L%
*/
import java.util.List;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.MethodOutcome;
import java.util.List;
/**
* This class is a replacement for {@link DaoMethodOutcome} for delete operations,
* as they can perform their operation over multiple resources

View File

@ -1,4 +1,5 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@ -14,29 +15,29 @@
<name>HAPI FHIR JPA Server</name>
<dependencies>
<!--
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
<!--
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
-->
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>net.sf.saxon</groupId>
@ -74,6 +75,11 @@
<artifactId>hapi-fhir-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server-empi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
@ -210,9 +216,9 @@
<!-- Patch Dependencies -->
<dependency>
<groupId>io.dogote</groupId>
<artifactId>json-patch</artifactId>
</dependency>
<groupId>io.dogote</groupId>
<artifactId>json-patch</artifactId>
</dependency>
<dependency>
<groupId>com.github.dnault</groupId>
<artifactId>xml-patch</artifactId>
@ -229,9 +235,9 @@
For some reason JavaDoc crashed during site generation unless we have this dependency
-->
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<scope>provided</scope>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<scope>provided</scope>
</dependency>
<!--
@ -256,7 +262,6 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
<scope>test</scope>
</dependency>
<dependency>
@ -281,12 +286,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-test-utilities</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.tomcat</groupId>
@ -294,7 +299,7 @@
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@ -381,8 +386,8 @@
<artifactId>javax.activation-api</artifactId>
</exclusion>
<!--<exclusion>-->
<!--<groupId>org.jboss.spec.javax.transaction</groupId>-->
<!--<artifactId>jboss-transaction-api_1.2_spec</artifactId>-->
<!--<groupId>org.jboss.spec.javax.transaction</groupId>-->
<!--<artifactId>jboss-transaction-api_1.2_spec</artifactId>-->
<!--</exclusion>-->
</exclusions>
</dependency>
@ -562,6 +567,11 @@
<artifactId>embedded-elasticsearch</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
@ -576,8 +586,6 @@
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>16.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
@ -591,11 +599,10 @@
<version>42.2.9</version>
</dependency>
</dependencies>
</dependencies>
<properties>
<skip-hib4>false</skip-hib4>
<jackson.version>2.7.1</jackson.version>
</properties>
@ -672,14 +679,14 @@
<version>${jaxb_runtime_version}</version>
</dependency>
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-core</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-core</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-impl</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-impl</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--</dependency>-->
</dependencies>
</plugin>
@ -703,7 +710,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<runOrder>alphabetical</runOrder>
<argLine>@{argLine} ${surefire_jvm_args} -XX:+HeapDumpOnOutOfMemoryError -XX:+CrashOnOutOfMemoryError</argLine>
<argLine>@{argLine} ${surefire_jvm_args}</argLine>
<forkCount>0.6C</forkCount>
<excludes>*StressTest*</excludes>
</configuration>
@ -722,7 +729,8 @@
<version>dstu2</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.dstu2</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu2.xml</targetResourceSpringBeansFile>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu2.xml
</targetResourceSpringBeansFile>
<baseResourceNames/>
<excludeResourceNames>
<!-- <excludeResourceName>OperationDefinition</excludeResourceName> <excludeResourceName>OperationOutcome</excludeResourceName> -->
@ -738,7 +746,8 @@
<version>dstu3</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.dstu3</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu3.xml</targetResourceSpringBeansFile>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu3.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames>
<excludeResourceNames>
</excludeResourceNames>
@ -753,7 +762,8 @@
<version>r4</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.r4</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r4.xml</targetResourceSpringBeansFile>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r4.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames>
<excludeResourceNames>
</excludeResourceNames>
@ -768,7 +778,8 @@
<version>r5</version>
<configPackageBase>ca.uhn.fhir.jpa.config</configPackageBase>
<packageBase>ca.uhn.fhir.jpa.rp.r5</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r5.xml</targetResourceSpringBeansFile>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-r5.xml
</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames>
</configuration>
</execution>
@ -837,12 +848,6 @@
</reporting>
<profiles>
<profile>
<id>JENKINS</id>
<properties>
<skip-hib4>true</skip-hib4>
</properties>
</profile>
<profile>
<id>NOPARALLEL</id>
<build>
@ -860,6 +865,21 @@
</plugins>
</build>
</profile>
<profile>
<id>CI</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>1</forkCount>
<runOrder>alphabetical</runOrder>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<!--
This profile is used on the Travis CI server because the full test suite
@ -891,6 +911,6 @@
</plugins>
</build>
</profile>
</profiles>
</profiles>
</project>

View File

@ -223,7 +223,7 @@ public class BinaryAccessProvider {
@Nonnull
private IBinaryTarget findAttachmentForRequest(IBaseResource theResource, String thePath, ServletRequestDetails theRequestDetails) {
Optional<IBase> type = myCtx.newFluentPath().evaluateFirst(theResource, thePath, IBase.class);
String resType = this.myCtx.getResourceDefinition(theResource).getName();
String resType = this.myCtx.getResourceType(theResource);
if (!type.isPresent()) {
String msg = this.myCtx.getLocalizer().getMessageSanitized(BinaryAccessProvider.class, "unknownPath", resType, thePath);
throw new InvalidRequestException(msg);

View File

@ -181,7 +181,7 @@ public class BinaryStorageInterceptor {
IIdType resourceId = theResource.getIdElement();
if (!resourceId.hasResourceType() && resourceId.hasIdPart()) {
String resourceType = myCtx.getResourceDefinition(theResource).getName();
String resourceType = myCtx.getResourceType(theResource);
resourceId = new IdType(resourceType + "/" + resourceId.getIdPart());
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.bulk;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.util.JsonUtil;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.Constants;
@ -31,6 +30,7 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ArrayUtil;
import ca.uhn.fhir.util.JsonUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;

View File

@ -378,7 +378,7 @@ public class BulkDataExportSvcImpl implements IBulkDataExportSvc {
// This is probably not a useful default, but having the default be "download the whole
// server" seems like a risky default too. We'll deal with that by having the default involve
// only returning a small time span
resourceTypes = myContext.getResourceNames();
resourceTypes = myContext.getResourceTypes();
if (since == null) {
since = DateUtils.addDays(new Date(), -1);
}

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
@ -102,7 +103,8 @@ import java.util.Date;
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*\\.test\\..*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.subscription.*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.searchparam.*")
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.searchparam.*"),
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.empi.*")
})
@Import({
SearchParamConfig.class
@ -121,6 +123,8 @@ public abstract class BaseConfig {
@Autowired
protected Environment myEnv;
@Autowired
private DaoRegistry myDaoRegistry;
@Bean("myDaoRegistry")
public DaoRegistry daoRegistry() {
@ -243,7 +247,7 @@ public abstract class BaseConfig {
* Subclasses may override
*/
protected boolean isSupported(String theResourceType) {
return daoRegistry().getResourceDaoOrNull(theResourceType) != null;
return myDaoRegistry.getResourceDaoOrNull(theResourceType) != null;
}
@Bean
@ -251,6 +255,12 @@ public abstract class BaseConfig {
return new JpaConsentContextServices();
}
@Bean
@Lazy
public DiffProvider diffProvider() {
return new DiffProvider();
}
@Bean
@Lazy
public IPartitionLookupSvc partitionConfigSvc() {

View File

@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu2;
import ca.uhn.fhir.jpa.term.TermReadSvcDstu2;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache;

View File

@ -50,6 +50,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
public class BaseR4Config extends BaseConfigDstu3Plus {
public static FhirContext ourFhirContext = FhirContext.forR4();
@Override
public FhirContext fhirContext() {
return fhirContextR4();
@ -64,7 +66,7 @@ public class BaseR4Config extends BaseConfigDstu3Plus {
@Bean
@Primary
public FhirContext fhirContextR4() {
FhirContext retVal = FhirContext.forR4();
FhirContext retVal = ourFhirContext;
// Don't strip versions in some places
ParserOptions parserOptions = retVal.getParserOptions();

View File

@ -964,11 +964,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceDefinition(theResourceType).getName();
return myContext.getResourceType(theResourceType);
}
String toResourceName(IBaseResource theResource) {
return myContext.getResourceDefinition(theResource).getName();
return myContext.getResourceType(theResource);
}
protected ResourceTable updateEntityForDelete(RequestDetails theRequest, TransactionDetails theTransactionDetails, ResourceTable entity) {
@ -997,7 +997,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
validateResourceForStorage((T) theResource, entity);
}
}
String resourceType = myContext.getResourceDefinition(theResource).getName();
String resourceType = myContext.getResourceType(theResource);
if (isNotBlank(entity.getResourceType()) && !entity.getResourceType().equals(resourceType)) {
throw new UnprocessableEntityException(
"Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
@ -1346,7 +1346,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
allowAny = true;
break;
}
validTypes.add(getContext().getResourceDefinition(nextValidType).getName());
validTypes.add(getContext().getResourceType(nextValidType));
}
if (allowAny) {
@ -1417,7 +1417,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
throw new UnprocessableEntityException("Resource contains the 'subsetted' tag, and must not be stored as it may contain a subset of available data");
}
String resName = getContext().getResourceDefinition(theResource).getName();
String resName = getContext().getResourceType(theResource);
validateChildReferences(theResource, resName);
validateMetaCount(totalMetaCount);

View File

@ -35,6 +35,7 @@ import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.patch.FhirPatch;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag;
@ -52,8 +53,8 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.CacheControlDirective;
@ -91,6 +92,7 @@ import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -856,7 +858,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, RequestDetails theRequest) {
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
ResourceTable entityToUpdate;
if (isNotBlank(theConditionalUrl)) {
@ -886,10 +888,20 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
IBaseResource destination;
if (thePatchType == PatchTypeEnum.JSON_PATCH) {
switch (thePatchType) {
case JSON_PATCH:
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
} else {
break;
case XML_PATCH:
destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
break;
case FHIR_PATCH_XML:
case FHIR_PATCH_JSON:
default:
IBaseParameters fhirPatchJson = theFhirPatchBody;
new FhirPatch(getContext()).apply(resourceToUpdate, fhirPatchJson);
destination = resourceToUpdate;
break;
}
@SuppressWarnings("unchecked")

View File

@ -34,13 +34,16 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.api.server.*;
import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceAccessDetails;
import ca.uhn.fhir.rest.api.server.SimplePreResourceShowDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.param.QualifierDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
@ -51,6 +54,8 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.InstantType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@ -58,7 +63,11 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_ERROR;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_INFO;
@ -66,6 +75,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseStorageDao {
private static final Logger ourLog = LoggerFactory.getLogger(BaseStorageDao.class);
@Autowired
protected ISearchParamRegistry mySearchParamRegistry;
@ -75,7 +85,7 @@ public abstract class BaseStorageDao {
* @param theResource The resource that is about to be stored
*/
protected void preProcessResourceForStorage(IBaseResource theResource) {
String type = getContext().getResourceDefinition(theResource).getName();
String type = getContext().getResourceType(theResource);
if (getResourceName() != null && !getResourceName().equals(type)) {
throw new InvalidRequestException(getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "incorrectResourceType", type, getResourceName()));
}
@ -213,7 +223,6 @@ public abstract class BaseStorageDao {
*/
protected abstract FhirContext getContext();
@Transactional(propagation = Propagation.SUPPORTS)
public void translateRawParameters(Map<String, List<String>> theSource, SearchParameterMap theTarget) {
if (theSource == null || theSource.isEmpty()) {

View File

@ -32,10 +32,8 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.model.DeleteConflict;
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
@ -47,6 +45,8 @@ import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -74,6 +74,7 @@ import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -350,7 +351,7 @@ public abstract class BaseTransactionProcessor {
// Do all entries have a verb?
for (int i = 0; i < myVersionAdapter.getEntries(theRequest).size(); i++) {
IBase nextReqEntry = requestEntries.get(i);
String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry);
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
if (verb == null || !isValidVerb(verb)) {
throw new InvalidRequestException(myContext.getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionEntryHasInvalidVerb", verb, i));
}
@ -372,7 +373,7 @@ public abstract class BaseTransactionProcessor {
for (int i = 0; i < requestEntries.size(); i++) {
originalRequestOrder.put(requestEntries.get(i), i);
myVersionAdapter.addEntry(response);
if (myVersionAdapter.getEntryRequestVerb(requestEntries.get(i)).equals("GET")) {
if (myVersionAdapter.getEntryRequestVerb(myContext, requestEntries.get(i)).equals("GET")) {
getEntries.add(requestEntries.get(i));
}
}
@ -544,7 +545,7 @@ public abstract class BaseTransactionProcessor {
IBase nextReqEntry = theEntries.get(index);
IBaseResource resource = myVersionAdapter.getResource(nextReqEntry);
if (resource != null) {
String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry);
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
String entryUrl = myVersionAdapter.getFullUrl(nextReqEntry);
String requestUrl = myVersionAdapter.getEntryRequestUrl(nextReqEntry);
String ifNoneExist = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
@ -647,8 +648,8 @@ public abstract class BaseTransactionProcessor {
}
String verb = myVersionAdapter.getEntryRequestVerb(nextReqEntry);
String resourceType = res != null ? myContext.getResourceDefinition(res).getName() : null;
String verb = myVersionAdapter.getEntryRequestVerb(myContext, nextReqEntry);
String resourceType = res != null ? myContext.getResourceType(res) : null;
Integer order = theOriginalRequestOrder.get(nextReqEntry);
IBase nextRespEntry = (IBase) myVersionAdapter.getEntries(theResponse).get(order);
@ -768,6 +769,8 @@ public abstract class BaseTransactionProcessor {
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
String patchBody = null;
String contentType = null;
IBaseParameters patchBodyParameters = null;
PatchTypeEnum patchType = null;
if (res instanceof IBaseBinary) {
IBaseBinary binary = (IBaseBinary) res;
@ -775,21 +778,26 @@ public abstract class BaseTransactionProcessor {
patchBody = new String(binary.getContent(), Charsets.UTF_8);
}
contentType = binary.getContentType();
patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(myContext, contentType);
if (patchType == PatchTypeEnum.FHIR_PATCH_JSON || patchType == PatchTypeEnum.FHIR_PATCH_XML) {
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "fhirPatchShouldNotUseBinaryResource");
throw new InvalidRequestException(msg);
}
} else if (res instanceof IBaseParameters) {
patchBodyParameters = (IBaseParameters) res;
patchType = PatchTypeEnum.FHIR_PATCH_JSON;
}
if (isBlank(patchBody)) {
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchBody");
throw new InvalidRequestException(msg);
}
if (isBlank(contentType)) {
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchContentType");
throw new InvalidRequestException(msg);
if (patchBodyParameters == null) {
if (isBlank(patchBody)) {
String msg = myContext.getLocalizer().getMessage(TransactionProcessor.class, "missingPatchBody");
throw new InvalidRequestException(msg);
}
}
IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb, url);
PatchTypeEnum patchType = PatchTypeEnum.forContentTypeOrThrowInvalidRequestException(contentType);
IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId());
DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, theRequest);
DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, patchBodyParameters, theRequest);
updatedEntities.add(outcome.getEntity());
if (outcome.getResource() != null) {
updatedResources.add(outcome.getResource());
@ -989,7 +997,7 @@ public abstract class BaseTransactionProcessor {
private String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceDefinition(theResourceType).getName();
return myContext.getResourceType(theResourceType);
}
public void setContext(FhirContext theContext) {
@ -1030,7 +1038,7 @@ public abstract class BaseTransactionProcessor {
}
private String toMatchUrl(IBase theEntry) {
String verb = myVersionAdapter.getEntryRequestVerb(theEntry);
String verb = myVersionAdapter.getEntryRequestVerb(myContext, theEntry);
if (verb.equals("POST")) {
return myVersionAdapter.getEntryIfNoneExist(theEntry);
}
@ -1069,7 +1077,7 @@ public abstract class BaseTransactionProcessor {
BUNDLEENTRY addEntry(BUNDLE theBundle);
String getEntryRequestVerb(BUNDLEENTRY theEntry);
String getEntryRequestVerb(FhirContext theContext, BUNDLEENTRY theEntry);
String getFullUrl(BUNDLEENTRY theEntry);
@ -1106,7 +1114,7 @@ public abstract class BaseTransactionProcessor {
//@formatter:off
public class TransactionSorter implements Comparator<IBase> {
private Set<String> myPlaceholderIds;
private final Set<String> myPlaceholderIds;
public TransactionSorter(Set<String> thePlaceholderIds) {
myPlaceholderIds = thePlaceholderIds;
@ -1159,8 +1167,8 @@ public abstract class BaseTransactionProcessor {
private int toOrder(IBase theO1) {
int o1 = 0;
if (myVersionAdapter.getEntryRequestVerb(theO1) != null) {
switch (myVersionAdapter.getEntryRequestVerb(theO1)) {
if (myVersionAdapter.getEntryRequestVerb(myContext, theO1) != null) {
switch (myVersionAdapter.getEntryRequestVerb(myContext, theO1)) {
case "DELETE":
o1 = 1;
break;
@ -1206,7 +1214,7 @@ public abstract class BaseTransactionProcessor {
}
private static String toStatusString(int theStatusCode) {
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
return theStatusCode + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}

View File

@ -0,0 +1,195 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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.empi.api.EmpiLinkSourceEnum;
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
import ca.uhn.fhir.empi.log.Logs;
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.entity.EmpiLink;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Service
public class EmpiLinkDaoSvc {
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
@Autowired
private IEmpiLinkDao myEmpiLinkDao;
@Autowired
private IdHelperService myIdHelperService;
public EmpiLink createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, @Nullable EmpiTransactionContext theEmpiTransactionContext) {
Long personPid = myIdHelperService.getPidOrNull(thePerson);
Long resourcePid = myIdHelperService.getPidOrNull(theTarget);
EmpiLink empiLink = getOrCreateEmpiLinkByPersonPidAndTargetPid(personPid, resourcePid);
empiLink.setLinkSource(theLinkSource);
empiLink.setMatchResult(theMatchResult);
String message = String.format("Creating EmpiLink from %s to %s -> %s", thePerson.getIdElement().toUnqualifiedVersionless(), theTarget.getIdElement().toUnqualifiedVersionless(), theMatchResult);
theEmpiTransactionContext.addTransactionLogMessage(message);
ourLog.debug(message);
save(empiLink);
return empiLink;
}
@Nonnull
public EmpiLink getOrCreateEmpiLinkByPersonPidAndTargetPid(Long thePersonPid, Long theResourcePid) {
Optional<EmpiLink> oExisting = getLinkByPersonPidAndTargetPid(thePersonPid, theResourcePid);
if (oExisting.isPresent()) {
return oExisting.get();
} else {
EmpiLink empiLink = new EmpiLink();
empiLink.setPersonPid(thePersonPid);
empiLink.setTargetPid(theResourcePid);
return empiLink;
}
}
public Optional<EmpiLink> getLinkByPersonPidAndTargetPid(Long thePersonPid, Long theTargetPid) {
if (theTargetPid == null || thePersonPid == null) {
return Optional.empty();
}
EmpiLink link = new EmpiLink();
link.setTargetPid(theTargetPid);
link.setPersonPid(thePersonPid);
Example<EmpiLink> example = Example.of(link);
return myEmpiLinkDao.findOne(example);
}
public List<EmpiLink> getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
EmpiLink exampleLink = new EmpiLink();
exampleLink.setTargetPid(theTargetPid);
exampleLink.setMatchResult(theMatchResult);
Example<EmpiLink> example = Example.of(exampleLink);
return myEmpiLinkDao.findAll(example);
}
public Optional<EmpiLink> getMatchedLinkForTargetPid(Long theTargetPid) {
EmpiLink exampleLink = new EmpiLink();
exampleLink.setTargetPid(theTargetPid);
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
Example<EmpiLink> example = Example.of(exampleLink);
return myEmpiLinkDao.findOne(example);
}
public Optional<EmpiLink> getMatchedLinkForTarget(IBaseResource theTarget) {
Long pid = myIdHelperService.getPidOrNull(theTarget);
if (pid == null) {
return Optional.empty();
}
EmpiLink exampleLink = new EmpiLink();
exampleLink.setTargetPid(pid);
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
Example<EmpiLink> example = Example.of(exampleLink);
return myEmpiLinkDao.findOne(example);
}
public Optional<EmpiLink> getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
EmpiLink exampleLink = new EmpiLink();
exampleLink.setPersonPid(thePersonPid);
exampleLink.setTargetPid(theTargetPid);
exampleLink.setMatchResult(theMatchResult);
Example<EmpiLink> example = Example.of(exampleLink);
return myEmpiLinkDao.findOne(example);
}
/**
* Get all {@link EmpiLink} which have {@link EmpiMatchResultEnum#POSSIBLE_DUPLICATE} as their match result.
*
* @return A list of EmpiLinks that hold potential duplicate persons.
*/
public List<EmpiLink> getPossibleDuplicates() {
EmpiLink exampleLink = new EmpiLink();
exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
Example<EmpiLink> example = Example.of(exampleLink);
return myEmpiLinkDao.findAll(example);
}
public Optional<EmpiLink> findEmpiLinkByTarget(IBaseResource theTargetResource) {
@Nullable Long pid = myIdHelperService.getPidOrNull(theTargetResource);
if (pid == null) {
return Optional.empty();
}
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
Example<EmpiLink> example = Example.of(empiLink);
return myEmpiLinkDao.findOne(example);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteLink(EmpiLink theEmpiLink) {
myEmpiLinkDao.delete(theEmpiLink);
}
/**
* Delete all EmpiLink records with any reference to this resource. (Used by Expunge.)
* @param theResource
* @return the number of records deleted
*/
public int deleteWithAnyReferenceTo(IBaseResource theResource) {
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null);
int removed = myEmpiLinkDao.deleteWithAnyReferenceToPid(pid);
if (removed > 0) {
ourLog.info("Removed {} EMPI links with references to {}", removed, theResource.getIdElement().toVersionless());
}
return removed;
}
public List<EmpiLink> findEmpiLinksByPersonId(IBaseResource thePersonResource) {
@Nullable Long pid = myIdHelperService.getPidOrNull(thePersonResource);
if (pid == null) {
return Collections.emptyList();
}
EmpiLink empiLink = new EmpiLink().setPersonPid(pid);
Example<EmpiLink> example = Example.of(empiLink);
return myEmpiLinkDao.findAll(example);
}
public EmpiLink save(EmpiLink theEmpiLink) {
if (theEmpiLink.getCreated() == null) {
theEmpiLink.setCreated(new Date());
}
theEmpiLink.setUpdated(new Date());
return myEmpiLinkDao.save(theEmpiLink);
}
public List<EmpiLink> findEmpiLinkByExample(Example<EmpiLink> theExampleLink) {
return myEmpiLinkDao.findAll(theExampleLink);
}
}

Some files were not shown because too many files have changed in this diff Show More