merge master

This commit is contained in:
Ken Stevens 2022-11-28 17:01:51 -05:00
commit d70be51003
188 changed files with 3488 additions and 3351 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,10 +22,13 @@ package ca.uhn.fhir.context.support;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.ClasspathUtil;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@ -321,6 +324,19 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
// Load built-in system
if (myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
String storageCodeEnum = ClasspathUtil.loadResource("org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json");
IBaseResource storageCodeCodeSystem = myCtx.newJsonParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(storageCodeEnum);
String url = myCtx.newTerser().getSinglePrimitiveValueOrNull(storageCodeCodeSystem, "url");
theCodeSystems.put(url, storageCodeCodeSystem);
}
}
private void loadStructureDefinitions(Map<String, IBaseResource> theCodeSystems, String theClasspath) {

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.model.api;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface ICodingEnum {
String getCode();
String getSystem();
String getDisplay();
}

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.io.Serializable;
@ -64,6 +65,23 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
// TODO: JA - Replace all of the various other get/put methods in subclasses with just using the two that are here
public T get(IBaseResource theResource) {
if (theResource instanceof IAnyResource) {
return (T) theResource.getUserData(name());
} else {
return (T) ((IResource)theResource).getResourceMetadata().get(this);
}
}
public void put(IBaseResource theResource, T theValue) {
if (theResource instanceof IAnyResource) {
theResource.setUserData(name(), theValue);
} else {
((IResource)theResource).getResourceMetadata().put(this, theValue);
}
}
/**
* If present and populated with a date/time (as an instance of {@link InstantDt}), this value is an indication that the resource is in the deleted state. This key is only used in a limited number
* of scenarios, such as POSTing transaction bundles to a server, or returning resource history.
@ -71,29 +89,19 @@ public abstract class ResourceMetadataKeyEnum<T> implements Serializable {
* Values for this key are of type <b>{@link InstantDt}</b>
* </p>
*/
public static final ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>> DELETED_AT = new ResourceMetadataKeySupportingAnyResource<InstantDt, IPrimitiveType<Date>>("DELETED_AT") {
public static final ResourceMetadataKeyEnum<IPrimitiveType<Date>> DELETED_AT = new ResourceMetadataKeyEnum<>("DELETED_AT") {
private static final long serialVersionUID = 1L;
@Override
public InstantDt get(IResource theResource) {
return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), DELETED_AT);
}
@SuppressWarnings("unchecked")
@Override
public IPrimitiveType<Date> get(IAnyResource theResource) {
return (IPrimitiveType<Date>) theResource.getUserData(DELETED_AT.name());
public IPrimitiveType<Date> get(IResource theResource) {
return (IPrimitiveType<Date>) theResource.getResourceMetadata().get(this);
}
@Override
public void put(IResource theResource, InstantDt theObject) {
theResource.getResourceMetadata().put(DELETED_AT, theObject);
public void put(IResource theResource, IPrimitiveType<Date> theObject) {
theResource.getResourceMetadata().put(this, theObject);
}
@Override
public void put(IAnyResource theResource, IPrimitiveType<Date> theObject) {
theResource.setUserData(DELETED_AT.name(), theObject);
}
};
/**

View File

@ -0,0 +1,72 @@
package ca.uhn.fhir.model.api;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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%
*/
/**
* This enum contains the allowable codes in the HAPI FHIR defined
* codesystem: https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code
*
* This is used in CRUD response OperationOutcome resources.
*/
public enum StorageResponseCodeEnum implements ICodingEnum {
SUCCESSFUL_CREATE("Create succeeded."),
SUCCESSFUL_CREATE_NO_CONDITIONAL_MATCH("Conditional create succeeded: no existing resource matched the conditional URL."),
SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH("Conditional create succeeded: an existing resource matched the conditional URL so no action was taken."),
SUCCESSFUL_UPDATE("Update succeeded."),
SUCCESSFUL_UPDATE_AS_CREATE("Update as create succeeded."),
SUCCESSFUL_UPDATE_NO_CHANGE("Update succeeded: No changes were detected so no action was taken."),
SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH("Conditional update succeeded: no existing resource matched the conditional URL so a new resource was created."),
SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH("Conditional update succeeded: an existing resource matched the conditional URL and was updated."),
SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE("Conditional update succeeded: an existing resource matched the conditional URL and was updated, but no changes were detected so no action was taken."),
SUCCESSFUL_DELETE("Delete succeeded."),
SUCCESSFUL_DELETE_ALREADY_DELETED("Delete succeeded: Resource was already deleted so no action was taken."),
SUCCESSFUL_DELETE_NOT_FOUND("Delete succeeded: No existing resource was found so no action was taken."),
SUCCESSFUL_PATCH("Patch succeeded."),
SUCCESSFUL_PATCH_NO_CHANGE("Patch succeeded: No changes were detected so no action was taken."),
SUCCESSFUL_CONDITIONAL_PATCH("Conditional patch succeeded."),
SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE("Conditional patch succeeded: No changes were detected so no action was taken.");
public static final String SYSTEM = "https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code";
private final String myDisplay;
StorageResponseCodeEnum(String theDisplay) {
myDisplay = theDisplay;
}
@Override
public String getCode() {
return name();
}
@Override
public String getSystem() {
return SYSTEM;
}
@Override
public String getDisplay() {
return myDisplay;
}
}

View File

@ -29,9 +29,12 @@ import org.apache.commons.lang3.Validate;
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.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;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
@ -40,7 +43,7 @@ import java.util.Objects;
* (method and search).
*
* <p>
*
* <p>
* This is not yet complete, and doesn't support all FHIR features. <b>USE WITH CAUTION</b> as the API
* may change.
*
@ -101,10 +104,8 @@ public class BundleBuilder {
/**
* Sets the specified primitive field on the bundle with the value provided.
*
* @param theFieldName
* Name of the primitive field.
* @param theFieldValue
* Value of the field to be set.
* @param theFieldName Name of the primitive field.
* @param theFieldValue Value of the field to be set.
*/
public BundleBuilder setBundleField(String theFieldName, String theFieldValue) {
BaseRuntimeChildDefinition typeChild = myBundleDef.getChildByName(theFieldName);
@ -119,12 +120,9 @@ public class BundleBuilder {
/**
* Sets the specified primitive field on the search entry with the value provided.
*
* @param theSearch
* Search part of the entry
* @param theFieldName
* Name of the primitive field.
* @param theFieldValue
* Value of the field to be set.
* @param theSearch Search part of the entry
* @param theFieldName Name of the primitive field.
* @param theFieldValue Value of the field to be set.
*/
public BundleBuilder setSearchField(IBase theSearch, String theFieldName, String theFieldValue) {
BaseRuntimeChildDefinition typeChild = mySearchDef.getChildByName(theFieldName);
@ -144,6 +142,37 @@ public class BundleBuilder {
return this;
}
/**
* Adds a FHIRPatch patch bundle to the transaction
* @param theTarget The target resource ID to patch
* @param thePatch The FHIRPath Parameters resource
* @since 6.3.0
*/
public PatchBuilder addTransactionFhirPatchEntry(IIdType theTarget, IBaseParameters thePatch) {
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(theTarget.getResourceType(), "theTarget must contain a resource type");
Validate.notBlank(theTarget.getIdPart(), "theTarget must contain an ID");
IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(thePatch, theTarget.getValue(), theTarget.toUnqualifiedVersionless().getValue(), "PATCH");
return new PatchBuilder(url);
}
/**
* Adds a FHIRPatch patch bundle to the transaction. This method is intended for conditional PATCH operations. If you
* know the ID of the resource you wish to patch, use {@link #addTransactionFhirPatchEntry(IIdType, IBaseParameters)}
* instead.
*
* @param thePatch The FHIRPath Parameters resource
* @since 6.3.0
* @see #addTransactionFhirPatchEntry(IIdType, IBaseParameters)
*/
public PatchBuilder addTransactionFhirPatchEntry(IBaseParameters thePatch) {
IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(thePatch, null, null, "PATCH");
return new PatchBuilder(url);
}
/**
* Adds an entry containing an update (PUT) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
@ -151,22 +180,39 @@ public class BundleBuilder {
* @param theResource The resource to update
*/
public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
IIdType id = theResource.getIdElement();
if (id.hasIdPart() && !id.hasResourceType()) {
String resourceType = myContext.getResourceType(theResource);
id = id.withResourceType(resourceType);
}
String requestUrl = id.toUnqualifiedVersionless().getValue();
String fullUrl = id.getValue();
String verb = "PUT";
IPrimitiveType<?> url = addAndPopulateTransactionBundleEntryRequest(theResource, fullUrl, requestUrl, verb);
return new UpdateBuilder(url);
}
@Nonnull
private IPrimitiveType<?> addAndPopulateTransactionBundleEntryRequest(IBaseResource theResource, String theFullUrl, String theRequestUrl, String theHttpVerb) {
setBundleField("type", "transaction");
IBase request = addEntryAndReturnRequest(theResource);
IBase request = addEntryAndReturnRequest(theResource, theFullUrl);
// Bundle.entry.request.url
IPrimitiveType<?> url = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
String resourceType = myContext.getResourceType(theResource);
url.setValueAsString(theResource.getIdElement().toUnqualifiedVersionless().withResourceType(resourceType).getValue());
url.setValueAsString(theRequestUrl);
myEntryRequestUrlChild.getMutator().setValue(request, url);
// Bundle.entry.request.url
// Bundle.entry.request.method
IPrimitiveType<?> method = (IPrimitiveType<?>) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("PUT");
method.setValueAsString(theHttpVerb);
myEntryRequestMethodChild.getMutator().setValue(request, method);
return new UpdateBuilder(url);
return url;
}
/**
@ -178,7 +224,7 @@ public class BundleBuilder {
public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) {
setBundleField("type", "transaction");
IBase request = addEntryAndReturnRequest(theResource);
IBase request = addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
String resourceType = myContext.getResourceType(theResource);
@ -198,15 +244,30 @@ public class BundleBuilder {
/**
* Adds an entry containing a delete (DELETE) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
*
* <p>
* Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry,
*
* @param theResource The resource to delete.
*/
public void addTransactionDeleteEntry(IBaseResource theResource) {
public DeleteBuilder addTransactionDeleteEntry(IBaseResource theResource) {
String resourceType = myContext.getResourceType(theResource);
String idPart = theResource.getIdElement().toUnqualifiedVersionless().getIdPart();
addTransactionDeleteEntry(resourceType, idPart);
return addTransactionDeleteEntry(resourceType, idPart);
}
/**
* Adds an entry containing a delete (DELETE) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
* <p>
* Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry,
*
* @param theResourceId The resource ID to delete.
* @return
*/
public DeleteBuilder addTransactionDeleteEntry(IIdType theResourceId) {
String resourceType = theResourceId.getResourceType();
String idPart = theResourceId.getIdPart();
return addTransactionDeleteEntry(resourceType, idPart);
}
/**
@ -214,24 +275,45 @@ public class BundleBuilder {
* Also sets the Bundle.type value to "transaction" if it is not already set.
*
* @param theResourceType The type resource to delete.
* @param theIdPart the ID of the resource to delete.
* @param theIdPart the ID of the resource to delete.
*/
public void addTransactionDeleteEntry(String theResourceType, String theIdPart) {
public DeleteBuilder addTransactionDeleteEntry(String theResourceType, String theIdPart) {
setBundleField("type", "transaction");
IBase request = addEntryAndReturnRequest();
IdDt idDt = new IdDt(theIdPart);
String deleteUrl = idDt.toUnqualifiedVersionless().withResourceType(theResourceType).getValue();
return addDeleteEntry(deleteUrl);
}
/**
* Adds an entry containing a delete (DELETE) request.
* Also sets the Bundle.type value to "transaction" if it is not already set.
*
* @param theMatchUrl The match URL, e.g. <code>Patient?identifier=http://foo|123</code>
* @since 6.3.0
*/
public BaseOperationBuilder addTransactionDeleteEntryConditional(String theMatchUrl) {
Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank");
return addDeleteEntry(theMatchUrl);
}
@Nonnull
private DeleteBuilder addDeleteEntry(String theDeleteUrl) {
IBase request = addEntryAndReturnRequest();
// Bundle.entry.request.url
IPrimitiveType<?> url = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
url.setValueAsString(idDt.toUnqualifiedVersionless().withResourceType(theResourceType).getValue());
url.setValueAsString(theDeleteUrl);
myEntryRequestUrlChild.getMutator().setValue(request, url);
// Bundle.entry.request.method
IPrimitiveType<?> method = (IPrimitiveType<?>) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments());
method.setValueAsString("DELETE");
myEntryRequestMethodChild.getMutator().setValue(request, method);
}
return new DeleteBuilder();
}
/**
@ -239,14 +321,13 @@ public class BundleBuilder {
*/
public void addCollectionEntry(IBaseResource theResource) {
setType("collection");
addEntryAndReturnRequest(theResource);
addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
}
/**
* Creates new entry and adds it to the bundle
*
* @return
* Returns the new entry.
* @return Returns the new entry.
*/
public IBase addEntry() {
IBase entry = myEntryDef.newInstance();
@ -258,8 +339,7 @@ public class BundleBuilder {
* Creates new search instance for the specified entry
*
* @param entry Entry to create search instance for
* @return
* Returns the search instance
* @return Returns the search instance
*/
public IBaseBackboneElement addSearch(IBase entry) {
IBase searchInstance = mySearchDef.newInstance();
@ -267,19 +347,14 @@ public class BundleBuilder {
return (IBaseBackboneElement) searchInstance;
}
/**
*
* @param theResource
* @return
*/
public IBase addEntryAndReturnRequest(IBaseResource theResource) {
private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) {
Validate.notNull(theResource, "theResource must not be null");
IBase entry = addEntry();
// Bundle.entry.fullUrl
IPrimitiveType<?> fullUrl = (IPrimitiveType<?>) myContext.getElementDefinition("uri").newInstance();
fullUrl.setValueAsString(theResource.getIdElement().getValue());
fullUrl.setValueAsString(theFullUrl);
myEntryFullUrlChild.getMutator().setValue(entry, fullUrl);
// Bundle.entry.resource
@ -306,6 +381,15 @@ public class BundleBuilder {
return myBundle;
}
/**
* Convenience method which auto-casts the results of {@link #getBundle()}
*
* @since 6.3.0
*/
public <T extends IBaseBundle> T getBundleTyped() {
return (T) myBundle;
}
public BundleBuilder setMetaField(String theFieldName, IBase theFieldValue) {
BaseRuntimeChildDefinition.IMutator mutator = myMetaDef.getChildByName(theFieldName).getMutator();
mutator.setValue(myBundle.getMeta(), theFieldValue);
@ -315,12 +399,9 @@ public class BundleBuilder {
/**
* Sets the specified entry field.
*
* @param theEntry
* The entry instance to set values on
* @param theEntryChildName
* The child field name of the entry instance to be set
* @param theValue
* The field value to set
* @param theEntry The entry instance to set values on
* @param theEntryChildName The child field name of the entry instance to be set
* @param theValue The field value to set
*/
public void addToEntry(IBase theEntry, String theEntryChildName, IBase theValue) {
addToBase(theEntry, theEntryChildName, theValue, myEntryDef);
@ -329,12 +410,9 @@ public class BundleBuilder {
/**
* Sets the specified search field.
*
* @param theSearch
* The search instance to set values on
* @param theSearchFieldName
* The child field name of the search instance to be set
* @param theSearchFieldValue
* The field value to set
* @param theSearch The search instance to set values on
* @param theSearchFieldName The child field name of the search instance to be set
* @param theSearchFieldValue The field value to set
*/
public void addToSearch(IBase theSearch, String theSearchFieldName, IBase theSearchFieldValue) {
addToBase(theSearch, theSearchFieldName, theSearchFieldValue, mySearchDef);
@ -349,12 +427,9 @@ public class BundleBuilder {
/**
* Creates a new primitive.
*
* @param theTypeName
* The element type for the primitive
* @param <T>
* Actual type of the parameterized primitive type interface
* @return
* Returns the new empty instance of the element definition.
* @param theTypeName The element type for the primitive
* @param <T> Actual type of the parameterized primitive type interface
* @return Returns the new empty instance of the element definition.
*/
public <T> IPrimitiveType<T> newPrimitive(String theTypeName) {
BaseRuntimeElementDefinition primitiveDefinition = myContext.getElementDefinition(theTypeName);
@ -365,14 +440,10 @@ public class BundleBuilder {
/**
* Creates a new primitive instance of the specified element type.
*
* @param theTypeName
* Element type to create
* @param theInitialValue
* Initial value to be set on the new instance
* @param <T>
* Actual type of the parameterized primitive type interface
* @return
* Returns the newly created instance
* @param theTypeName Element type to create
* @param theInitialValue Initial value to be set on the new instance
* @param <T> Actual type of the parameterized primitive type interface
* @return Returns the newly created instance
*/
public <T> IPrimitiveType<T> newPrimitive(String theTypeName, T theInitialValue) {
IPrimitiveType<T> retVal = newPrimitive(theTypeName);
@ -389,38 +460,84 @@ public class BundleBuilder {
setBundleField("type", theType);
}
public static class UpdateBuilder {
private final IPrimitiveType<?> myUrl;
public class DeleteBuilder extends BaseOperationBuilder {
public UpdateBuilder(IPrimitiveType<?> theUrl) {
myUrl = theUrl;
}
// nothing yet
/**
* Make this update a Conditional Update
*/
public void conditional(String theConditionalUrl) {
myUrl.setValueAsString(theConditionalUrl);
}
}
public class CreateBuilder {
public class PatchBuilder extends BaseOperationBuilderWithConditionalUrl<PatchBuilder> {
PatchBuilder(IPrimitiveType<?> theUrl) {
super(theUrl);
}
}
public class UpdateBuilder extends BaseOperationBuilderWithConditionalUrl<UpdateBuilder> {
UpdateBuilder(IPrimitiveType<?> theUrl) {
super(theUrl);
}
}
public class CreateBuilder extends BaseOperationBuilder {
private final IBase myRequest;
public CreateBuilder(IBase theRequest) {
CreateBuilder(IBase theRequest) {
myRequest = theRequest;
}
/**
* Make this create a Conditional Create
*/
public void conditional(String theConditionalUrl) {
public CreateBuilder conditional(String theConditionalUrl) {
BaseRuntimeElementDefinition<?> stringDefinition = Objects.requireNonNull(myContext.getElementDefinition("string"));
IPrimitiveType<?> ifNoneExist = (IPrimitiveType<?>) stringDefinition.newInstance();
ifNoneExist.setValueAsString(theConditionalUrl);
myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist);
return this;
}
}
public abstract class BaseOperationBuilder {
/**
* Returns a reference to the BundleBuilder instance.
*
* Calling this method has no effect at all, it is only
* provided for easy method chaning if you want to build
* your bundle as a single fluent call.
*
* @since 6.3.0
*/
public BundleBuilder andThen() {
return BundleBuilder.this;
}
}
public abstract class BaseOperationBuilderWithConditionalUrl<T extends BaseOperationBuilder> extends BaseOperationBuilder {
private final IPrimitiveType<?> myUrl;
BaseOperationBuilderWithConditionalUrl(IPrimitiveType<?> theUrl) {
myUrl = theUrl;
}
/**
* Make this update a Conditional Update
*/
@SuppressWarnings("unchecked")
public T conditional(String theConditionalUrl) {
myUrl.setValueAsString(theConditionalUrl);
return (T) this;
}
}

View File

@ -34,6 +34,9 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import java.util.zip.GZIPInputStream;
@ -86,6 +89,10 @@ public class ClasspathUtil {
return retVal;
}
public static Reader loadResourceAsReader(String theClasspath) {
return new InputStreamReader(loadResourceAsStream(theClasspath), StandardCharsets.UTF_8);
}
/**
* Load a classpath resource, throw an {@link InternalErrorException} if not found
*/

View File

@ -29,11 +29,13 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nullable;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -53,8 +55,12 @@ public class OperationOutcomeUtil {
* @return Returns the newly added issue
*/
public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) {
return addIssue(theCtx, theOperationOutcome, theSeverity, theDetails, theLocation, theCode, null, null, null);
}
public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode, @Nullable String theDetailSystem, @Nullable String theDetailCode, @Nullable String theDetailDescription) {
IBase issue = createIssue(theCtx, theOperationOutcome);
populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode);
populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode, theDetailSystem, theDetailCode, theDetailDescription);
return issue;
}
@ -127,17 +133,17 @@ public class OperationOutcomeUtil {
}
}
private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode) {
private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode, String theDetailSystem, String theDetailCode, String theDetailDescription) {
BaseRuntimeElementCompositeDefinition<?> issueElement = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(theIssue.getClass());
BaseRuntimeChildDefinition detailsChild;
detailsChild = issueElement.getChildByName("diagnostics");
BaseRuntimeChildDefinition diagnosticsChild;
diagnosticsChild = issueElement.getChildByName("diagnostics");
BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code");
IPrimitiveType<?> codeElem = (IPrimitiveType<?>) codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments());
codeElem.setValueAsString(theCode);
codeChild.getMutator().addValue(theIssue, codeElem);
BaseRuntimeElementDefinition<?> stringDef = detailsChild.getChildByName(detailsChild.getElementName());
BaseRuntimeElementDefinition<?> stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName());
BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity");
IPrimitiveType<?> severityElem = (IPrimitiveType<?>) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments());
@ -146,9 +152,27 @@ public class OperationOutcomeUtil {
IPrimitiveType<?> string = (IPrimitiveType<?>) stringDef.newInstance();
string.setValueAsString(theDetails);
detailsChild.getMutator().setValue(theIssue, string);
diagnosticsChild.getMutator().setValue(theIssue, string);
addLocationToIssue(theCtx, theIssue, theLocation);
if (isNotBlank(theDetailSystem)) {
BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details");
if (detailsChild != null) {
BaseRuntimeElementDefinition<?> codeableConceptDef = theCtx.getElementDefinition("CodeableConcept");
IBase codeableConcept = codeableConceptDef.newInstance();
BaseRuntimeElementDefinition<?> codingDef = theCtx.getElementDefinition("Coding");
IBaseCoding coding = (IBaseCoding) codingDef.newInstance();
coding.setSystem(theDetailSystem);
coding.setCode(theDetailCode);
coding.setDisplay(theDetailDescription);
codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding);
detailsChild.getMutator().addValue(theIssue, codeableConcept);
}
}
}
public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) {

View File

@ -107,6 +107,7 @@ public enum VersionEnum {
V6_1_4,
V6_2_0,
V6_2_1,
// Dev Build
V6_3_0
;

View File

@ -99,10 +99,22 @@ ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidParameterChain=Invalid parameter chain
ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidVersion=Version "{0}" is not valid for resource {1}
ca.uhn.fhir.jpa.dao.BaseStorageDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true
ca.uhn.fhir.jpa.dao.BaseStorageDao.missingBody=No body was supplied in request
ca.uhn.fhir.jpa.dao.BaseStorageDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreate=Successfully created resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
ca.uhn.fhir.jpa.dao.BaseStorageDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Nothing has been deleted.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreate=Successfully created resource "{0}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreateConditionalNoMatch=Successfully conditionally created resource "{0}". No existing resources matched URL "{1}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreateConditionalWithMatch=Successfully conditionally created resource "{0}". Existing resource matched URL "{1}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatch=Successfully patched resource "{0}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchNoChange=Successfully patched resource "{0}" with no changes detected.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchConditional=Successfully conditionally patched resource. Existing resource {0} matched URL: {1}.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchConditionalNoChange=Successfully conditionally patched resource with no changes detected. Existing resource {0} matched URL: {1}.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdate=Successfully updated resource "{0}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateNoChange=Successfully updated resource "{0}" with no changes detected.
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateAsCreate=Successfully created resource "{0}" using update as create (ie. create with client assigned ID).
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalWithMatch=Successfully conditionally updated resource "{0}". Existing resource matched URL "{1}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalNoMatch=Successfully conditionally updated resource "{0}". Created resource because no existing resource matched URL "{1}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalNoChangeWithMatch=Successfully conditionally updated resource "{0}" with no changes detected. Existing resource matched URL "{1}".
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulDeletes=Successfully deleted {0} resource(s).
ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulTimingSuffix=Took {0}ms.
ca.uhn.fhir.jpa.dao.BaseStorageDao.deleteResourceNotExisting=Not deleted, resource {0} does not exist.
ca.uhn.fhir.jpa.dao.BaseStorageDao.deleteResourceAlreadyDeleted=Not deleted, resource {0} was already deleted.
ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2}
@ -130,7 +142,7 @@ ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissin
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.matchesFound=Matches found
ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.noMatchesFound=No Matches found
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
ca.uhn.fhir.jpa.dao.JpaResourceDaoSearchParameter.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter
ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForServer=The :text modifier is disabled on this server

View File

@ -32,6 +32,15 @@ public class ClasspathUtilTest {
}
}
@Test
public void testLoadResourceAsReaderNotFound() {
try {
ClasspathUtil.loadResourceAsReader("/FOOOOOO");
} catch (InternalErrorException e) {
assertEquals(Msg.code(1758) + "Unable to find classpath resource: /FOOOOOO", e.getMessage());
}
}
/**
* Should not throw any exception
*/

View File

@ -8,6 +8,7 @@ import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VersionEnumTest {
@ -18,10 +19,25 @@ public class VersionEnumTest {
.collect(Collectors.toList());
String version = VersionUtil.getVersion();
version = "V" + version.replace(".", "_");
version = version.replaceAll("-PRE[0-9]+", "");
version = version.replace("-SNAPSHOT", "");
String[] parts = version.split("\\.");
assertEquals(3, parts.length);
int major = Integer.parseInt(parts[0]);
int minor = Integer.parseInt(parts[1]);
int patch = Integer.parseInt(parts[2]);
if (major >= 6 && minor >= 3) {
if (minor % 2 == 1) {
patch = 0;
}
}
version = major + "." + minor + "." + patch;
version = "V" + version.replace(".", "_");
assertThat(versions, hasItem(version));
}

View File

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -47,9 +47,12 @@ import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ConceptMap;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.model.CapabilityStatement;
import org.hl7.fhir.r5.model.SearchParameter;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
/**
* This class converts versions of various resources to/from a canonical version
* of the resource. The specific version that is considered canonical is arbitrary
@ -68,14 +71,13 @@ public class VersionCanonicalizer {
private static final BaseAdvisor_10_50 ADVISOR_10_50 = new BaseAdvisor_10_50(false);
private static final BaseAdvisor_40_50 ADVISOR_40_50 = new BaseAdvisor_40_50(false);
private static final BaseAdvisor_43_50 ADVISOR_43_50 = new BaseAdvisor_43_50(false);
@SuppressWarnings("rawtypes")
private final IStrategy myStrategy;
public VersionCanonicalizer(FhirContext theTargetContext) {
this(theTargetContext.getVersion().getVersion());
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
@SuppressWarnings({"EnumSwitchStatementWhichMissesCases", "EnhancedSwitchMigration"})
public VersionCanonicalizer(FhirVersionEnum theTargetVersion) {
switch (theTargetVersion) {
case DSTU2:
@ -160,9 +162,13 @@ public class VersionCanonicalizer {
return myStrategy.conceptMapToCanonical(theConceptMap);
}
private interface IStrategy<T extends IBaseResource> {
public <T extends IBaseResource> SearchParameter searchParameterToCanonical(T theSearchParameter) {
return myStrategy.searchParameterToCanonical(theSearchParameter);
}
CapabilityStatement capabilityStatementToCanonical(T theCapabilityStatement);
private interface IStrategy {
CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement);
Coding codingToCanonical(IBaseCoding theCoding);
@ -175,9 +181,11 @@ public class VersionCanonicalizer {
IBaseResource valueSetFromCanonical(ValueSet theValueSet);
ConceptMap conceptMapToCanonical(IBaseResource theConceptMap);
SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter);
}
private class Dstu2Strategy implements IStrategy<ca.uhn.fhir.model.dstu2.resource.BaseResource> {
private static class Dstu2Strategy implements IStrategy {
private final FhirContext myDstu2Hl7OrgContext = FhirContext.forDstu2Hl7OrgCached();
@ -189,7 +197,7 @@ public class VersionCanonicalizer {
}
@Override
public CapabilityStatement capabilityStatementToCanonical(ca.uhn.fhir.model.dstu2.resource.BaseResource theCapabilityStatement) {
public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) {
org.hl7.fhir.dstu2.model.Resource reencoded = reencodeToHl7Org(theCapabilityStatement);
return (CapabilityStatement) VersionConvertorFactory_10_50.convertResource(reencoded, ADVISOR_10_50);
}
@ -224,8 +232,7 @@ public class VersionCanonicalizer {
@Override
public ValueSet valueSetToCanonical(IBaseResource theValueSet) {
org.hl7.fhir.dstu2.model.Resource reencoded = reencodeToHl7Org(theValueSet);
ValueSet valueSet = (ValueSet) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40);
return valueSet;
return (ValueSet) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40);
}
@Override
@ -275,6 +282,16 @@ public class VersionCanonicalizer {
return (ConceptMap) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40);
}
@Override
public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) {
org.hl7.fhir.dstu2.model.SearchParameter reencoded = (org.hl7.fhir.dstu2.model.SearchParameter) reencodeToHl7Org(theSearchParameter);
SearchParameter retVal = (SearchParameter) VersionConvertorFactory_10_50.convertResource(reencoded, ADVISOR_10_50);
if (isBlank(retVal.getExpression())) {
retVal.setExpression(reencoded.getXpath());
}
return retVal;
}
private Resource reencodeToHl7Org(IBaseResource theInput) {
if (myHl7OrgStructures) {
return (Resource) theInput;
@ -291,11 +308,11 @@ public class VersionCanonicalizer {
}
private class Dstu3Strategy implements IStrategy<org.hl7.fhir.dstu3.model.Resource> {
private static class Dstu3Strategy implements IStrategy {
@Override
public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.dstu3.model.Resource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_30_50.convertResource(theCapabilityStatement, ADVISOR_30_50);
public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_30_50.convertResource((org.hl7.fhir.dstu3.model.Resource) theCapabilityStatement, ADVISOR_30_50);
}
@Override
@ -327,12 +344,17 @@ public class VersionCanonicalizer {
public ConceptMap conceptMapToCanonical(IBaseResource theConceptMap) {
return (ConceptMap) VersionConvertorFactory_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theConceptMap, ADVISOR_30_40);
}
@Override
public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) {
return (SearchParameter) VersionConvertorFactory_30_50.convertResource((org.hl7.fhir.dstu3.model.Resource) theSearchParameter, ADVISOR_30_50);
}
}
private class R4Strategy implements IStrategy<org.hl7.fhir.r4.model.Resource> {
private static class R4Strategy implements IStrategy {
@Override
public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r4.model.Resource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource(theCapabilityStatement, ADVISOR_40_50);
public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r4.model.Resource) theCapabilityStatement, ADVISOR_40_50);
}
@Override
@ -365,13 +387,18 @@ public class VersionCanonicalizer {
return (ConceptMap) theConceptMap;
}
@Override
public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) {
return (SearchParameter) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r4.model.Resource) theSearchParameter, ADVISOR_40_50);
}
}
private class R4BStrategy implements IStrategy<org.hl7.fhir.r4b.model.Resource> {
private static class R4BStrategy implements IStrategy {
@Override
public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r4b.model.Resource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_43_50.convertResource(theCapabilityStatement, ADVISOR_43_50);
public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) {
return (CapabilityStatement) VersionConvertorFactory_43_50.convertResource((org.hl7.fhir.r4b.model.Resource) theCapabilityStatement, ADVISOR_43_50);
}
@Override
@ -410,13 +437,18 @@ public class VersionCanonicalizer {
return (ConceptMap) VersionConvertorFactory_40_50.convertResource(conceptMapR5, ADVISOR_40_50);
}
@Override
public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) {
return (SearchParameter) VersionConvertorFactory_43_50.convertResource((org.hl7.fhir.r4b.model.Resource) theSearchParameter, ADVISOR_43_50);
}
}
private class R5Strategy implements IStrategy<org.hl7.fhir.r5.model.Resource> {
private static class R5Strategy implements IStrategy {
@Override
public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r5.model.Resource theCapabilityStatement) {
public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) {
return (CapabilityStatement) theCapabilityStatement;
}
@ -450,6 +482,11 @@ public class VersionCanonicalizer {
return (ConceptMap) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r5.model.ConceptMap) theConceptMap, ADVISOR_40_50);
}
@Override
public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) {
return (SearchParameter) theSearchParameter;
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -26,6 +26,11 @@ import ca.uhn.fhir.util.BundleBuilder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import java.math.BigDecimal;
@ -109,6 +114,53 @@ public class BundleBuilderExamples {
//END SNIPPET: createConditional
}
public void patch() throws FHIRException {
//START SNIPPET: patch
// Create a FHIR Patch object
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation");
op.addPart().setName("type").setValue(new CodeType("replace"));
op.addPart().setName("path").setValue(new CodeType("Patient.active"));
op.addPart().setName("value").setValue(new BooleanType(false));
// Create a TransactionBuilder
BundleBuilder builder = new BundleBuilder(myFhirContext);
// Create a target object (this is the ID of the resource that will be patched)
IIdType targetId = new IdType("Patient/123");
// Add the patch to the bundle
builder.addTransactionFhirPatchEntry(targetId, patch);
// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: patch
}
public void patchConditional() throws FHIRException {
//START SNIPPET: patchConditional
// Create a FHIR Patch object
Parameters patch = new Parameters();
Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation");
op.addPart().setName("type").setValue(new CodeType("replace"));
op.addPart().setName("path").setValue(new CodeType("Patient.active"));
op.addPart().setName("value").setValue(new BooleanType(false));
// Create a TransactionBuilder
BundleBuilder builder = new BundleBuilder(myFhirContext);
// Add the patch to the bundle with a conditional URL
String conditionalUrl = "Patient?identifier=http://foo|123";
builder.addTransactionFhirPatchEntry(patch).conditional(conditionalUrl);
// Execute the transaction
IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute();
//END SNIPPET: patchConditional
}
public void customizeBundle() throws FHIRException {
//START SNIPPET: customizeBundle
// Create a TransactionBuilder

View File

@ -1,3 +1,3 @@
---
release-date: "FILL ME"
release-date: "2022-07-08"
codename: "Sojourner"

View File

@ -0,0 +1,6 @@
---
type: add
issue: 4293
title: "The BundleBuilder now supports adding conditional
DELETE operations, PATCH operations, and conditional PATCH
operations to a transaction bundle."

View File

@ -0,0 +1,11 @@
---
type: add
issue: 4293
title: "When performing create/update/patch/delete operations against the JPA server, the response
OperationOutcome will now include additional details about the outcome of the operation. This
includes:
<ul>
<li>For updates, the message will indicate the the update did not contain any changes (i.e. a No-op)</li>
<li>For conditional creates/updates/deletes, the message will indicate whether the conditional URL matched any existing resources and the outcome of the operation.</li>
<li>A new coding has been added to the <code>OperationOutcome.issue.details.coding</code> containing a machine processable equivalent to the outcome.</li>
</ul>"

View File

@ -0,0 +1,7 @@
---
type: add
issue: 4293
title: "When updating resources using a FHIR transaction in the JPA server, if the
client instructs the server to include the resource body in the response, any
tags that have been carried forward from previous versions of the resource are
now included in the response."

View File

@ -6,6 +6,7 @@
<ul>
<li>SLF4j (All): 1.7.33 -> 2.0.3</li>
<li>Logback (All): 1.2.10 -> 1.4.4</li>
<li>Woodstox-Core (XML Parser): 6.3.1 -> 6.4.0</li>
<li>log4j-to-slf4j (JPA): 2.17.1 -> 2.19.0</li>
<li>Jetty (CLI): 9.4.48.v20220622 -> 10.0.12</li>
<li>Spring Boot: 2.7.4 -> 2.7.5</li>

View File

@ -2,13 +2,13 @@
The following diagram shows the request processing pipeline for processing a server request. You may click on the diagram to enlarge it.
<a href="/hapi-fhir/docs/images/interceptors-server-pipeline.svg" target="_blank">Expand</a>
<img src="/hapi-fhir/docs/images/interceptors-server-pipeline.svg" alt="Server Pipeline"/>
<a href="server_pointcuts/interceptors-server-pipeline.svg" target="_blank">Expand</a>
<img src="server_pointcuts/interceptors-server-pipeline.svg" alt="Server Pipeline"/>
# Storage / JPA Server Pointcuts
The following diagram shows the request processing pipeline for processing a server request. You may click on the diagram to enlarge it.
<a href="/hapi-fhir/docs/images/interceptors-server-jpa-pipeline.svg" target="_blank">Expand</a>
<img src="/hapi-fhir/docs/images/interceptors-server-jpa-pipeline.svg" alt="Server Pipeline"/>
<a href="server_pointcuts/interceptors-server-jpa-pipeline.svg" target="_blank">Expand</a>
<img src="server_pointcuts/interceptors-server-jpa-pipeline.svg" alt="Server Pipeline"/>

View File

@ -36,7 +36,23 @@ If you want to perform a conditional update:
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|updateConditional}}
```
# Customizing bundle
# Transaction Patch
To add a PATCH operation to a transaction bundle:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|patch}}
```
## Conditional Patch
If you want to perform a conditional patch:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|patchConditional}}
```
# Customizing the Bundle
If you want to manipulate a bundle:

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -39,14 +39,16 @@ public class SqlQuery {
private final StackTraceElement[] myStackTrace;
private final int mySize;
private final LanguageEnum myLanguage;
private final String myNamespace;
public SqlQuery(String theSql, List<String> theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize) {
this(theSql, theParams, theQueryTimestamp, theElapsedTime, theStackTraceElements, theSize, LanguageEnum.SQL);
this(null, theSql, theParams, theQueryTimestamp, theElapsedTime, theStackTraceElements, theSize, LanguageEnum.SQL);
}
public SqlQuery(String theSql, List<String> theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize, LanguageEnum theLanguage) {
public SqlQuery(String theNamespace, String theSql, List<String> theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize, LanguageEnum theLanguage) {
Validate.notNull(theLanguage, "theLanguage must not be null");
myNamespace = theNamespace;
mySql = theSql;
myParams = Collections.unmodifiableList(theParams);
myQueryTimestamp = theQueryTimestamp;
@ -56,6 +58,10 @@ public class SqlQuery {
myLanguage = theLanguage;
}
public String getNamespace() {
return myNamespace;
}
public long getQueryTimestamp() {
return myQueryTimestamp;
}
@ -118,6 +124,10 @@ public class SqlQuery {
return mySize;
}
@Override
public String toString() {
return getSql(true, true);
}
public enum LanguageEnum {
@ -125,9 +135,4 @@ public class SqlQuery {
JSON
}
@Override
public String toString() {
return getSql(true, true);
}
}

View File

@ -384,7 +384,7 @@ public class TestUtil {
}
public static void sleepOneClick() {
ca.uhn.fhir.util.TestUtil.sleepAtLeast(1);
ca.uhn.fhir.util.TestUtil.sleepAtLeast(1, false);
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -84,7 +84,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRED)
public String storeWorkChunk(BatchWorkChunk theBatchWorkChunk) {
Batch2WorkChunkEntity entity = new Batch2WorkChunkEntity();
entity.setId(UUID.randomUUID().toString());
@ -102,7 +102,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRED)
public Optional<WorkChunk> fetchWorkChunkSetStartTimeAndMarkInProgress(String theChunkId) {
myWorkChunkRepository.updateChunkStatusForStart(theChunkId, new Date(), StatusEnum.IN_PROGRESS);
Optional<Batch2WorkChunkEntity> chunk = myWorkChunkRepository.findById(theChunkId);
@ -110,7 +110,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRED)
public String storeNewInstance(JobInstance theInstance) {
Validate.isTrue(isBlank(theInstance.getInstanceId()));
@ -271,7 +271,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence {
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Transactional(propagation = Propagation.REQUIRED)
public boolean canAdvanceInstanceToNextStep(String theInstanceId, String theCurrentStepId) {
List<StatusEnum> statusesForStep = myWorkChunkRepository.getDistinctStatusesForStep(theInstanceId, theCurrentStepId);
ourLog.debug("Checking whether gated job can advanced to next step. [instanceId={}, currentStepId={}, statusesForStep={}]", theInstanceId, theCurrentStepId, statusesForStep);

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService;
import ca.uhn.fhir.jpa.delete.batch2.DeleteExpungeSqlBuilder;
import ca.uhn.fhir.jpa.delete.batch2.DeleteExpungeSvcImpl;
import ca.uhn.fhir.jpa.reindex.Batch2DaoSvcImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.persistence.EntityManager;
@ -43,7 +44,7 @@ public class Batch2SupportConfig {
}
@Bean
public IDeleteExpungeSvc deleteExpungeSvc(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, IFulltextSearchSvc theFullTextSearchSvc) {
public IDeleteExpungeSvc deleteExpungeSvc(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, @Autowired(required = false) IFulltextSearchSvc theFullTextSearchSvc) {
return new DeleteExpungeSvcImpl(theEntityManager, theDeleteExpungeSqlBuilder, theFullTextSearchSvc);
}

View File

@ -25,8 +25,11 @@ import ca.uhn.fhir.jpa.dao.DaoSearchParamProvider;
import ca.uhn.fhir.jpa.dao.HistoryBuilder;
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.JpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService;
@ -116,10 +119,14 @@ import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl;
import ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl;
import ca.uhn.fhir.jpa.term.TermReadSvcImpl;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
@ -225,7 +232,6 @@ public class JpaConfig {
return new ResponseTerminologyTranslationInterceptor(theValidationSupport, theResponseTerminologyTranslationSvc);
}
@Lazy
@Bean
public ResponseTerminologyTranslationSvc responseTerminologyTranslationSvc(IValidationSupport theValidationSupport) {
return new ResponseTerminologyTranslationSvc(theValidationSupport);
@ -260,6 +266,11 @@ public class JpaConfig {
return new ValueSetOperationProvider();
}
@Bean
public IJpaStorageResourceParser jpaStorageResourceParser() {
return new JpaStorageResourceParser();
}
@Bean
public TransactionProcessor transactionProcessor() {
return new TransactionProcessor();
@ -763,4 +774,22 @@ public class JpaConfig {
return new TermReadSvcImpl();
}
@Bean
public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() {
return new TermCodeSystemStorageSvcImpl();
}
@Bean
public ITermReindexingSvc termReindexingSvc() {
return new TermReindexingSvcImpl();
}
@Bean
public ObservationLastNIndexPersistSvc baseObservationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistSvc();
}
}

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.jpa.config;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.jpa.dao.ObservationLastNIndexPersistSvc;
import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl;
import ca.uhn.fhir.jpa.term.TermConceptDaoSvc;
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SharedConfigDstu3Plus {
@Bean
public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() {
return new TermCodeSystemStorageSvcImpl();
}
@Bean
public ITermReindexingSvc termReindexingSvc() {
return new TermReindexingSvcImpl();
}
@Bean
public ObservationLastNIndexPersistSvc baseObservationLastNIndexpersistSvc() {
return new ObservationLastNIndexPersistSvc();
}
}

View File

@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigDstu3;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@Import({
FhirContextDstu3Config.class,
GeneratedDaoAndResourceProviderConfigDstu3.class,
SharedConfigDstu3Plus.class,
JpaConfig.class
})
public class JpaDstu3Config {

View File

@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
@ -22,7 +21,6 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Consent;
@ -62,7 +60,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@Import({
FhirContextR4Config.class,
GeneratedDaoAndResourceProviderConfigR4.class,
SharedConfigDstu3Plus.class,
JpaConfig.class
})
public class JpaR4Config {

View File

@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4B;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.r4b.TransactionProcessorVersionAdapterR4B;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@Import({
FhirContextR4BConfig.class,
GeneratedDaoAndResourceProviderConfigR4B.class,
SharedConfigDstu3Plus.class,
JpaConfig.class
})
public class JpaR4BConfig {

View File

@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR5;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
@Import({
FhirContextR5Config.class,
GeneratedDaoAndResourceProviderConfigR5.class,
SharedConfigDstu3Plus.class,
JpaConfig.class
})
public class JpaR5Config {

View File

@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.api.dao.IJpaDao;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
@ -29,8 +30,6 @@ import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
@ -46,11 +45,9 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
@ -61,6 +58,7 @@ import ca.uhn.fhir.jpa.util.AddRemoveCount;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.StorageResponseCodeEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
@ -72,13 +70,11 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.HistorySearchStyleEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
@ -86,6 +82,8 @@ import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.CoverageIgnore;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.MetaUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.XmlUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
@ -148,11 +146,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.model.util.JpaConstants.ALL_PARTITIONS_NAME;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -181,9 +177,18 @@ import static org.apache.commons.lang3.StringUtils.trim;
* #L%
*/
/**
* TODO: JA - This class has only one subclass now. Historically it was a common
* ancestor for BaseHapiFhirSystemDao and BaseHapiFhirResourceDao but I've untangled
* the former from this hierarchy in order to simplify moving common functionality
* for resource DAOs into the hapi-fhir-storage project. This class should be merged
* into BaseHapiFhirResourceDao, but that should be done in its own dedicated PR
* since it'll be a noisy change.
*/
@SuppressWarnings("WeakerAccess")
@Repository
public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStorageDao implements IDao, IJpaDao<T>, ApplicationContextAware {
public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStorageResourceDao<T> implements IDao, IJpaDao<T>, ApplicationContextAware {
public static final long INDEX_STATUS_INDEXED = 1L;
public static final long INDEX_STATUS_INDEXING_FAILED = 2L;
@ -233,8 +238,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
@Autowired
private IPartitionLookupSvc myPartitionLookupSvc;
@Autowired
private MemoryCacheService myMemoryCacheService;
@ -243,6 +246,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
@Autowired
private PlatformTransactionManager myTransactionManager;
@Autowired
protected IJpaStorageResourceParser myJpaStorageResourceParser;
@VisibleForTesting
public void setSearchParamPresenceSvc(ISearchParamPresenceSvc theSearchParamPresenceSvc) {
@ -371,14 +376,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
myContext = theContext;
}
public FhirContext getContext(FhirVersionEnum theVersion) {
Validate.notNull(theVersion, "theVersion must not be null");
if (theVersion == myFhirContext.getVersion().getVersion()) {
return myFhirContext;
}
return FhirContext.forCached(theVersion);
}
/**
* <code>null</code> will only be returned if the scheme and tag are both blank
*/
@ -513,27 +510,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return retVal;
}
protected IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset) {
return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null);
}
protected IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType) {
String resourceName = defaultIfBlank(theResourceType, null);
Search search = new Search();
search.setOffset(theOffset);
search.setDeleted(false);
search.setCreated(new Date());
search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive);
search.setUuid(UUID.randomUUID().toString());
search.setResourceType(resourceName);
search.setResourceId(theResourcePid);
search.setSearchType(SearchTypeEnum.HISTORY);
search.setStatus(SearchStatusEnum.FINISHED);
search.setHistorySearchStyle(searchParameterType);
return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search);
}
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
String newVersion;
@ -796,133 +772,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return !allTagsOld.equals(allTagsNew);
}
@SuppressWarnings("unchecked")
private <R extends IBaseResource> R populateResourceMetadataHapi(Class<R> theResourceType, IBaseResourceEntity theEntity, @Nullable Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IResource res, Long theVersion) {
R retVal = (R) res;
if (theEntity.getDeleted() != null) {
res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance();
retVal = (R) res;
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
if (theForHistoryOperation) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
}
} else if (theForHistoryOperation) {
/*
* If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
*/
Date published = theEntity.getPublished().getValue();
Date updated = theEntity.getUpdated().getValue();
if (published.equals(updated)) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
} else {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
}
}
res.setId(theEntity.getIdDt().withVersion(theVersion.toString()));
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
if (theTagList != null) {
if (theEntity.isHasTags()) {
TagList tagList = new TagList();
List<IBaseCoding> securityLabels = new ArrayList<>();
List<IdDt> profiles = new ArrayList<>();
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
case PROFILE:
profiles.add(new IdDt(next.getTag().getCode()));
break;
case SECURITY_LABEL:
IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt();
secLabel.setSystem(next.getTag().getSystem());
secLabel.setCode(next.getTag().getCode());
secLabel.setDisplay(next.getTag().getDisplay());
securityLabels.add(secLabel);
break;
case TAG:
tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay()));
break;
}
}
if (tagList.size() > 0) {
ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList);
}
if (securityLabels.size() > 0) {
ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels));
}
if (profiles.size() > 0) {
ResourceMetadataKeyEnum.PROFILES.put(res, profiles);
}
}
}
return retVal;
}
@SuppressWarnings("unchecked")
private <R extends IBaseResource> R populateResourceMetadataRi(Class<R> theResourceType, IBaseResourceEntity theEntity, @Nullable Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IAnyResource res, Long theVersion) {
R retVal = (R) res;
if (theEntity.getDeleted() != null) {
res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance();
retVal = (R) res;
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
if (theForHistoryOperation) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode());
}
} else if (theForHistoryOperation) {
/*
* If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
*/
Date published = theEntity.getPublished().getValue();
Date updated = theEntity.getUpdated().getValue();
if (published.equals(updated)) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode());
} else {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode());
}
}
res.getMeta().setLastUpdated(null);
res.getMeta().setVersionId(null);
updateResourceMetadata(theEntity, res);
res.setId(res.getIdElement().withVersion(theVersion.toString()));
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
if (theTagList != null) {
res.getMeta().getTag().clear();
res.getMeta().getProfile().clear();
res.getMeta().getSecurity().clear();
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
case PROFILE:
res.getMeta().addProfile(next.getTag().getCode());
break;
case SECURITY_LABEL:
IBaseCoding sec = res.getMeta().addSecurity();
sec.setSystem(next.getTag().getSystem());
sec.setCode(next.getTag().getCode());
sec.setDisplay(next.getTag().getDisplay());
break;
case TAG:
IBaseCoding tag = res.getMeta().addTag();
tag.setSystem(next.getTag().getSystem());
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
break;
}
}
}
return retVal;
}
/**
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
@ -954,6 +803,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
// nothing
}
@Override
@CoverageIgnore
public BaseHasResource readEntity(IIdType theValueId, RequestDetails theRequest) {
throw new NotImplementedException(Msg.code(927) + "");
@ -1005,220 +855,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return metaSnapshotModeTokens.contains(theTag.getTag().getTagType());
}
@Override
public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
Class<? extends IBaseResource> resourceType = type.getImplementingClass();
return toResource(resourceType, theEntity, null, theForHistoryOperation);
}
@SuppressWarnings("unchecked")
@Override
public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
// 1. get resource, it's encoding and the tags if any
byte[] resourceBytes;
String resourceText;
ResourceEncodingEnum resourceEncoding;
@Nullable
Collection<? extends BaseTag> tagList = Collections.emptyList();
long version;
String provenanceSourceUri = null;
String provenanceRequestId = null;
if (theEntity instanceof ResourceHistoryTable) {
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
resourceBytes = history.getResource();
resourceText = history.getResourceTextVc();
resourceEncoding = history.getEncoding();
switch (getConfig().getTagStorageMode()) {
case VERSIONED:
default:
if (history.isHasTags()) {
tagList = history.getTags();
}
break;
case NON_VERSIONED:
if (history.getResourceTable().isHasTags()) {
tagList = history.getResourceTable().getTags();
}
break;
case INLINE:
tagList = null;
}
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
provenanceSourceUri = history.getProvenance().getSourceUri();
}
} else if (theEntity instanceof ResourceTable) {
ResourceTable resource = (ResourceTable) theEntity;
ResourceHistoryTable history;
if (resource.getCurrentVersionEntity() != null) {
history = resource.getCurrentVersionEntity();
} else {
version = theEntity.getVersion();
history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
((ResourceTable) theEntity).setCurrentVersionEntity(history);
while (history == null) {
if (version > 1L) {
version--;
history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
} else {
return null;
}
}
}
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
resourceText = history.getResourceTextVc();
switch (getConfig().getTagStorageMode()) {
case VERSIONED:
case NON_VERSIONED:
if (resource.isHasTags()) {
tagList = resource.getTags();
} else {
tagList = Collections.emptyList();
}
break;
case INLINE:
tagList = null;
break;
}
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
provenanceSourceUri = history.getProvenance().getSourceUri();
}
} else if (theEntity instanceof ResourceSearchView) {
// This is the search View
ResourceSearchView view = (ResourceSearchView) theEntity;
resourceBytes = view.getResource();
resourceText = view.getResourceTextVc();
resourceEncoding = view.getEncoding();
version = view.getVersion();
provenanceRequestId = view.getProvenanceRequestId();
provenanceSourceUri = view.getProvenanceSourceUri();
switch (getConfig().getTagStorageMode()) {
case VERSIONED:
case NON_VERSIONED:
if (theTagList != null) {
tagList = theTagList;
} else {
tagList = Collections.emptyList();
}
break;
case INLINE:
tagList = null;
break;
}
} else {
// something wrong
return null;
}
// 2. get The text
String decodedResourceText;
if (resourceText != null) {
decodedResourceText = resourceText;
} else {
decodedResourceText = decodeResource(resourceBytes, resourceEncoding);
}
// 3. Use the appropriate custom type if one is specified in the context
Class<R> resourceType = theResourceType;
if (tagList != null) {
if (myContext.hasDefaultTypeForProfile()) {
for (BaseTag nextTag : tagList) {
if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
String profile = nextTag.getTag().getCode();
if (isNotBlank(profile)) {
Class<? extends IBaseResource> newType = myContext.getDefaultTypeForProfile(profile);
if (newType != null && theResourceType.isAssignableFrom(newType)) {
ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile);
resourceType = (Class<R>) newType;
break;
}
}
}
}
}
}
// 4. parse the text to FHIR
R retVal;
if (resourceEncoding != ResourceEncodingEnum.DEL) {
LenientErrorHandler errorHandler = new LenientErrorHandler(false).setErrorOnInvalidValue(false);
IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), errorHandler, theEntity.getId());
try {
retVal = parser.parseResource(resourceType, decodedResourceText);
} catch (Exception e) {
StringBuilder b = new StringBuilder();
b.append("Failed to parse database resource[");
b.append(myFhirContext.getResourceType(resourceType));
b.append("/");
b.append(theEntity.getIdDt().getIdPart());
b.append(" (pid ");
b.append(theEntity.getId());
b.append(", version ");
b.append(theEntity.getFhirVersion().name());
b.append("): ");
b.append(e.getMessage());
String msg = b.toString();
ourLog.error(msg, e);
throw new DataFormatException(Msg.code(928) + msg, e);
}
} else {
retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance();
}
// 5. fill MetaData
retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, resourceType, retVal);
// 6. Handle source (provenance)
if (isNotBlank(provenanceRequestId) || isNotBlank(provenanceSourceUri)) {
String sourceString = cleanProvenanceSourceUri(provenanceSourceUri)
+ (isNotBlank(provenanceRequestId) ? "#" : "")
+ defaultString(provenanceRequestId);
MetaUtil.setSource(myContext, retVal, sourceString);
}
// 7. Add partition information
if (myPartitionSettings.isPartitioningEnabled()) {
PartitionablePartitionId partitionId = theEntity.getPartitionId();
if (partitionId != null && partitionId.getPartitionId() != null) {
PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId());
retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId());
} else {
retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null);
}
}
return retVal;
}
protected <R extends IBaseResource> R populateResourceMetadata(IBaseResourceEntity theEntity, boolean theForHistoryOperation, @Nullable Collection<? extends BaseTag> tagList, long theVersion, Class<R> theResourceType, R theResource) {
if (theResource instanceof IResource) {
IResource res = (IResource) theResource;
theResource = populateResourceMetadataHapi(theResourceType, theEntity, tagList, theForHistoryOperation, res, theVersion);
} else {
IAnyResource res = (IAnyResource) theResource;
theResource = populateResourceMetadataRi(theResourceType, theEntity, tagList, theForHistoryOperation, res, theVersion);
}
return theResource;
}
public String toResourceName(Class<? extends IBaseResource> theResourceType) {
return myContext.getResourceType(theResourceType);
}
String toResourceName(IBaseResource theResource) {
return myContext.getResourceType(theResource);
@ -1375,7 +1013,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange() && (entity.getVersion() > 1 || theUpdateVersion)) {
ourLog.debug("Resource {} has not changed", entity.getIdDt().toUnqualified().getValue());
if (theResource != null) {
updateResourceMetadata(entity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(entity, theResource);
}
entity.setUnchangedInCurrentOperation(true);
return entity;
@ -1475,7 +1113,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
if (theResource != null) {
updateResourceMetadata(entity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(entity, theResource);
}
@ -1498,7 +1136,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (getConfig().isMassIngestionMode()) {
oldResource = null;
} else {
oldResource = toResource(entity, false);
oldResource = myJpaStorageResourceParser.toResource(entity, false);
}
notifyInterceptors(theRequest, theResource, oldResource, theTransactionDetails, true);
@ -1510,7 +1148,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
historyEntity = ((ResourceTable) readEntity(theResourceId, theRequest)).getCurrentVersionEntity();
// Update version/lastUpdated so that interceptors see the correct version
updateResourceMetadata(savedEntity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(savedEntity, theResource);
// Populate the PID in the resource, so it is available to hooks
addPidToResource(savedEntity, theResource);
@ -1537,7 +1175,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (!changed && myConfig.isSuppressUpdatesWithNoChange() && (historyEntity.getVersion() > 1)) {
ourLog.debug("Resource {} has not changed", historyEntity.getIdDt().toUnqualified().getValue());
updateResourceMetadata(historyEntity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(historyEntity, theResource);
return historyEntity;
}
@ -1556,7 +1194,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
historyEntity.setResourceTextVc(encodedResource.getResourceText());
myResourceHistoryTableDao.save(historyEntity);
updateResourceMetadata(historyEntity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(historyEntity, theResource);
return historyEntity;
}
@ -1586,14 +1224,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
/**
* TODO eventually consider refactoring this to be part of an interceptor.
*
* <p>
* Throws an exception if the partition of the request, and the partition of the existing entity do not match.
*
* @param theRequest the request.
* @param entity the existing entity.
* @param entity the existing entity.
*/
private void failIfPartitionMismatch(RequestDetails theRequest, ResourceTable entity) {
if (myPartitionSettings.isPartitioningEnabled() && theRequest != null && theRequest.getTenantId() != null && entity.getPartitionId() != null &&
theRequest.getTenantId() != ALL_PARTITIONS_NAME) {
!ALL_PARTITIONS_NAME.equals(theRequest.getTenantId())) {
PartitionEntity partitionEntity = myPartitionLookupSvc.getPartitionByName(theRequest.getTenantId());
//partitionEntity should never be null
if (partitionEntity != null && !partitionEntity.getId().equals(entity.getPartitionId().getPartitionId())) {
@ -1668,8 +1307,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
@Override
public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion,
IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) {
public DaoMethodOutcome updateInternal(RequestDetails theRequestDetails, T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion,
IBasePersistedResource theEntity, IIdType theResourceId, @Nullable IBaseResource theOldResource, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) {
ResourceTable entity = (ResourceTable) theEntity;
@ -1696,7 +1335,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
// Update version/lastUpdated so that interceptors see the correct version
updateResourceMetadata(savedEntity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(savedEntity, theResource);
// Populate the PID in the resource so it is available to hooks
addPidToResource(savedEntity, theResource);
@ -1706,7 +1345,42 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
notifyInterceptors(theRequestDetails, theResource, theOldResource, theTransactionDetails, false);
}
return savedEntity;
Collection<? extends BaseTag> tagList = Collections.emptyList();
if (entity.isHasTags()) {
tagList = entity.getTags();
}
long version = entity.getVersion();
myJpaStorageResourceParser.populateResourceMetadata(entity, false, tagList, version, theResource);
boolean wasDeleted = false;
// NB If this if-else ever gets collapsed, make sure to account for possible null (will happen in mass-ingestion mode)
if (theOldResource instanceof IResource) {
wasDeleted = ResourceMetadataKeyEnum.DELETED_AT.get((IResource) theOldResource) != null;
} else if (theOldResource instanceof IAnyResource) {
wasDeleted = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) theOldResource) != null;
}
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, theResource, theMatchUrl, theOperationType).setCreated(wasDeleted);
if (!thePerformIndexing) {
IIdType id = getContext().getVersion().newIdType();
id.setValue(entity.getIdDt().getValue());
outcome.setId(id);
}
// Only include a task timer if we're not in a sub-request (i.e. a transaction)
// since individual item times don't actually make much sense in the context
// of a transaction
StopWatch w = null;
if (theRequestDetails != null && !theRequestDetails.isSubRequest()) {
if (theTransactionDetails != null && !theTransactionDetails.isFhirTransaction()) {
w = new StopWatch(theTransactionDetails.getTransactionDate());
}
}
populateOperationOutcomeForUpdate(w, outcome, theMatchUrl, outcome.getOperationType());
return outcome;
}
private void notifyInterceptors(RequestDetails theRequestDetails, T theResource, IBaseResource theOldResource, TransactionDetails theTransactionDetails, boolean isUnchanged) {
@ -1735,26 +1409,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
}
}
protected void updateResourceMetadata(IBaseResourceEntity theEntity, IBaseResource theResource) {
IIdType id = theEntity.getIdDt();
if (getContext().getVersion().getVersion().isRi()) {
id = getContext().getVersion().newIdType().setValue(id.getValue());
}
if (id.hasResourceType() == false) {
id = id.withResourceType(theEntity.getResourceType());
}
theResource.setId(id);
if (theResource instanceof IResource) {
ResourceMetadataKeyEnum.VERSION.put((IResource) theResource, id.getVersionIdPart());
ResourceMetadataKeyEnum.UPDATED.put((IResource) theResource, theEntity.getUpdated());
} else {
IBaseMetaType meta = theResource.getMeta();
meta.setVersionId(id.getVersionIdPart());
meta.setLastUpdated(theEntity.getUpdatedDate());
}
}
private void validateChildReferenceTargetTypes(IBase theElement, String thePath) {
if (theElement == null) {
@ -1896,6 +1551,22 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
myPartitionSettings = thePartitionSettings;
}
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
private final TagDefinition myTagDefinition;
private final MemoryCacheService.TagDefinitionCacheKey myKey;
public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) {
myTagDefinition = theTagDefinition;
myKey = theKey;
}
@Override
public void afterCommit() {
myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition);
}
}
@Nonnull
public static MemoryCacheService.TagDefinitionCacheKey toTagDefinitionMemoryCacheKey(TagTypeEnum theTagType, String theScheme, String theTerm) {
return new MemoryCacheService.TagDefinitionCacheKey(theTagType, theScheme, theTerm);
@ -1999,34 +1670,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
}
private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
for (IBaseCoding next : theSecurityLabels) {
retVal.add((BaseCodingDt) next);
}
return retVal;
}
public static void validateResourceType(BaseHasResource theEntity, String theResourceName) {
if (!theResourceName.equals(theEntity.getResourceType())) {
throw new ResourceNotFoundException(Msg.code(935) + "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType());
}
}
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
private final TagDefinition myTagDefinition;
private final MemoryCacheService.TagDefinitionCacheKey myKey;
public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) {
myTagDefinition = theTagDefinition;
myKey = theKey;
}
@Override
public void afterCommit() {
myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition);
}
/**
* Do not call this method outside of unit tests
*/
@VisibleForTesting
public void setJpaStorageResourceParserForUnitTest(IJpaStorageResourceParser theJpaStorageResourceParser) {
myJpaStorageResourceParser = theJpaStorageResourceParser;
}
}

View File

@ -58,26 +58,23 @@ import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.patch.FhirPatch;
import ca.uhn.fhir.jpa.patch.JsonPatchUtils;
import ca.uhn.fhir.jpa.patch.XmlPatchUtils;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceSearch;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.StorageResponseCodeEnum;
import ca.uhn.fhir.model.dstu2.resource.ListResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -105,9 +102,9 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import ca.uhn.fhir.validation.IValidationContext;
@ -119,7 +116,6 @@ 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;
@ -161,7 +157,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
public static final String BASE_RESOURCE_NAME = "resource";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired(required = false)
@ -182,18 +177,37 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private IDeleteExpungeJobSubmitter myDeleteExpungeJobSubmitter;
@Autowired
private IJobCoordinator myJobCoordinator;
private IInstanceValidatorModule myInstanceValidator;
private String myResourceName;
private Class<T> myResourceType;
@Autowired
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
@Autowired
private MemoryCacheService myMemoryCacheService;
private TransactionTemplate myTxTemplate;
@Autowired
private UrlPartitioner myUrlPartitioner;
@Override
protected HapiTransactionService getTransactionService() {
return myTransactionService;
}
@VisibleForTesting
public void setTransactionService(HapiTransactionService theTransactionService) {
myTransactionService = theTransactionService;
}
@Override
protected MatchResourceUrlService getMatchResourceUrlService() {
return myMatchResourceUrlService;
}
@Override
protected IStorageResourceParser getStorageResourceParser() {
return myJpaStorageResourceParser;
}
/**
* @deprecated Use {@link #create(T, RequestDetails)} instead
*/
@ -220,11 +234,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return create(theResource, theIfNoneExist, true, new TransactionDetails(), theRequestDetails);
}
@VisibleForTesting
public void setTransactionService(HapiTransactionService theTransactionService) {
myTransactionService = theTransactionService;
}
@Override
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
return myTransactionService.execute(theRequestDetails, theTransactionDetails, tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails));
@ -260,14 +269,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
return doCreateForPostOrPut(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId);
return doCreateForPostOrPut(theRequestDetails, theResource, theIfNoneExist, true, thePerformIndexing, requestPartitionId, RestOperationTypeEnum.CREATE, theTransactionDetails);
}
/**
* Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails)}
* as well as for FHIR update (PUT) where we're doing a create-with-client-assigned-ID (via {@link #doUpdate(IBaseResource, String, boolean, boolean, RequestDetails, TransactionDetails)}.
*/
private DaoMethodOutcome doCreateForPostOrPut(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
private DaoMethodOutcome doCreateForPostOrPut(RequestDetails theRequest, T theResource, String theMatchUrl, boolean theProcessMatchUrl, boolean thePerformIndexing, RequestPartitionId theRequestPartitionId, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
@ -276,22 +285,21 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
entity.setPartitionId(myRequestPartitionHelperService.toStoragePartition(theRequestPartitionId));
entity.setCreatedByMatchUrl(theIfNoneExist);
entity.setCreatedByMatchUrl(theMatchUrl);
entity.setVersion(1);
if (isNotBlank(theIfNoneExist)) {
Set<JpaPid> match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest)
.stream().map(id -> (JpaPid) id).collect(Collectors.toSet());
if (isNotBlank(theMatchUrl) && theProcessMatchUrl) {
Set<ResourcePersistentId> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest);
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size());
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theMatchUrl, match.size());
throw new PreconditionFailedException(Msg.code(958) + msg);
} else if (match.size() == 1) {
JpaPid pid = match.iterator().next();
JpaPid pid = (JpaPid) match.iterator().next();
Supplier<LazyDaoMethodOutcome.EntityAndResource> entitySupplier = () -> {
return myTxTemplate.execute(tx -> {
ResourceTable foundEntity = myEntityManager.find(ResourceTable.class, pid.getId());
IBaseResource resource = toResource(foundEntity, false);
IBaseResource resource = myJpaStorageResourceParser.toResource(foundEntity, false);
theResource.setId(resource.getIdElement().getValue());
return new LazyDaoMethodOutcome.EntityAndResource(foundEntity, resource);
});
@ -316,7 +324,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
});
};
return toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true);
DaoMethodOutcome outcome = toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true);
StorageResponseCodeEnum responseCode = StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH;
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalWithMatch", w.getMillisAndRestart(), UrlUtil.sanitizeUrlPart(theMatchUrl));
outcome.setOperationOutcome(createInfoOperationOutcome(msg, responseCode));
return outcome;
}
}
@ -387,15 +399,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
theTransactionDetails.addResolvedResourceId(jpaPid.getAssociatedResourceId(), jpaPid);
// Pre-cache the match URL
if (theIfNoneExist != null) {
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, jpaPid);
if (theMatchUrl != null) {
myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theMatchUrl, jpaPid);
}
// Update the version/last updated in the resource so that interceptors get
// the correct version
// TODO - the above updateEntity calls updateResourceMetadata
// Maybe we don't need this call here?
updateResourceMetadata(entity, theResource);
myJpaStorageResourceParser.updateResourceMetadata(entity, theResource);
// Populate the PID in the resource so it is available to hooks
addPidToResource(entity, theResource);
@ -411,15 +423,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams);
}
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(true);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource, theMatchUrl, theOperationType)
.setCreated(true);
if (!thePerformIndexing) {
outcome.setId(theResource.getIdElement());
}
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart());
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
populateOperationOutcomeForUpdate(w, outcome, theMatchUrl, theOperationType);
ourLog.debug(msg);
return outcome;
}
@ -533,8 +545,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// if not found, return an outcome anyways.
// Because no object actually existed, we'll
// just set the id and nothing else
DaoMethodOutcome outcome = createMethodOutcomeForResourceId(theId.getValue(), MESSAGE_KEY_DELETE_RESOURCE_NOT_EXISTING);
return outcome;
return createMethodOutcomeForResourceId(theId.getValue(), MESSAGE_KEY_DELETE_RESOURCE_NOT_EXISTING, StorageResponseCodeEnum.SUCCESSFUL_DELETE_NOT_FOUND);
}
if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) {
@ -543,7 +554,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Don't delete again if it's already deleted
if (isDeleted(entity)) {
DaoMethodOutcome outcome = createMethodOutcomeForResourceId(entity.getIdDt().getValue(), MESSAGE_KEY_DELETE_RESOURCE_ALREADY_DELETED);
DaoMethodOutcome outcome = createMethodOutcomeForResourceId(entity.getIdDt().getValue(), MESSAGE_KEY_DELETE_RESOURCE_ALREADY_DELETED, StorageResponseCodeEnum.SUCCESSFUL_DELETE_ALREADY_DELETED);
// used to exist, so we'll set the persistent id
outcome.setPersistentId(new JpaPid(entity.getResourceId()));
@ -554,7 +565,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
StopWatch w = new StopWatch();
T resourceToDelete = toResource(myResourceType, entity, null, false);
T resourceToDelete = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false);
theDeleteConflicts.setResourceIdMarkedForDeletion(theId);
// Notify IServerOperationInterceptors about pre-action call
@ -583,14 +594,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true);
DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete, null, RestOperationTypeEnum.DELETE).setCreated(true);
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseStorageDao.class, "successfulDeletes", 1, w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
outcome.setOperationOutcome(oo);
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulDeletes", 1);
msg += " " + getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulTimingSuffix", w.getMillis());
outcome.setOperationOutcome(createInfoOperationOutcome(msg, StorageResponseCodeEnum.SUCCESSFUL_DELETE));
return outcome;
}
@ -671,7 +679,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entity = myEntityManager.find(ResourceTable.class, ((JpaPid) pid).getId());
deletedResources.add(entity);
T resourceToDelete = toResource(myResourceType, entity, null, false);
T resourceToDelete = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false);
// Notify IServerOperationInterceptors about pre-action call
HookParams hooks = new HookParams()
@ -705,17 +713,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IBaseOperationOutcome oo;
if (deletedResources.isEmpty()) {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getMessageSanitized("unableToDeleteNotFound", theUrl);
String severity = "warning";
String code = "not-found";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "unableToDeleteNotFound", theUrl);
oo = createOperationOutcome(OO_SEVERITY_WARN, msg, "not-found", StorageResponseCodeEnum.SUCCESSFUL_DELETE_NOT_FOUND);
} else {
oo = OperationOutcomeUtil.newInstance(getContext());
String message = getContext().getLocalizer().getMessage(BaseStorageDao.class, "successfulDeletes", deletedResources.size(), w.getMillis());
String severity = "information";
String code = "informational";
OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code);
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulDeletes", deletedResources.size());
msg += " " + getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulTimingSuffix", w.getMillis());
oo = createInfoOperationOutcome(msg, StorageResponseCodeEnum.SUCCESSFUL_DELETE);
}
ourLog.debug("Processed delete on {} (matched {} resource(s)) in {}ms", theUrl, deletedResources.size(), w.getMillis());
@ -747,7 +750,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource theEntity, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
IBaseResource oldVersion = toResource(theEntity, false);
IBaseResource oldVersion = myJpaStorageResourceParser.toResource(theEntity, false);
List<TagDefinition> tags = toTagList(theMetaAdd);
for (TagDefinition nextDef : tags) {
@ -780,7 +783,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
myEntityManager.merge(theEntity);
// Interceptor call: STORAGE_PRECOMMIT_RESOURCE_UPDATED
IBaseResource newVersion = toResource(theEntity, false);
IBaseResource newVersion = myJpaStorageResourceParser.toResource(theEntity, false);
HookParams preStorageParams = new HookParams()
.add(IBaseResource.class, oldVersion)
.add(IBaseResource.class, newVersion)
@ -804,7 +807,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
private <MT extends IBaseMetaType> void doMetaDelete(MT theMetaDel, BaseHasResource theEntity, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
// todo mb update hibernate search index if we are storing resources - it assumes inline tags.
IBaseResource oldVersion = toResource(theEntity, false);
IBaseResource oldVersion = myJpaStorageResourceParser.toResource(theEntity, false);
List<TagDefinition> tags = toTagList(theMetaDel);
@ -826,7 +829,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
theEntity = myEntityManager.merge(theEntity);
// Interceptor call: STORAGE_PRECOMMIT_RESOURCE_UPDATED
IBaseResource newVersion = toResource(theEntity, false);
IBaseResource newVersion = myJpaStorageResourceParser.toResource(theEntity, false);
HookParams preStorageParams = new HookParams()
.add(IBaseResource.class, oldVersion)
.add(IBaseResource.class, newVersion)
@ -891,6 +894,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
@Override
@Nonnull
public String getResourceName() {
return myResourceName;
}
@ -910,7 +914,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
@Transactional
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
StopWatch w = new StopWatch();
IBundleProvider retVal = super.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset);
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset);
ourLog.debug("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart());
return retVal;
}
@ -926,7 +930,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
BaseHasResource entity = readEntity(id, theRequest);
IBundleProvider retVal = super.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset);
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset);
ourLog.debug("Processed history on {} in {}ms", id, w.getMillisAndRestart());
return retVal;
@ -941,7 +945,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless();
BaseHasResource entity = readEntity(id, theRequest);
IBundleProvider retVal = super.history(theRequest, myResourceName, entity.getId(),
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(),
theHistorySearchDateRangeParam.getLowerBoundAsInstant(),
theHistorySearchDateRangeParam.getUpperBoundAsInstant(),
theHistorySearchDateRangeParam.getOffset(),
@ -1098,68 +1102,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return toMetaDt(theType, tagDefinitions);
}
@Override
public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) {
TransactionDetails transactionDetails = new TransactionDetails();
return myTransactionService.execute(theRequest, transactionDetails, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest, transactionDetails));
}
private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
ResourceTable entityToUpdate;
if (isNotBlank(theConditionalUrl)) {
Set<JpaPid> match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theTransactionDetails, theRequest)
.stream().map(id -> (JpaPid) id).collect(Collectors.toSet());
if (match.size() > 1) {
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size());
throw new PreconditionFailedException(Msg.code(972) + msg);
} else if (match.size() == 1) {
JpaPid pid = match.iterator().next();
entityToUpdate = myEntityManager.find(ResourceTable.class, pid.getId());
} else {
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "invalidMatchUrlNoMatches", theConditionalUrl);
throw new ResourceNotFoundException(Msg.code(973) + msg);
}
} else {
entityToUpdate = readEntityLatestVersion(theId, theRequest, theTransactionDetails);
if (theId.hasVersionIdPart()) {
if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) {
throw new ResourceVersionConflictException(Msg.code(974) + "Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch");
}
}
}
validateResourceType(entityToUpdate);
if (isDeleted(entityToUpdate)) {
throw createResourceGoneException(entityToUpdate);
}
IBaseResource resourceToUpdate = toResource(entityToUpdate, false);
IBaseResource destination;
switch (thePatchType) {
case JSON_PATCH:
destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody);
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")
T destinationCasted = (T) destination;
myFhirContext.newJsonParser().setParserErrorHandler(new StrictErrorHandler()).encodeResourceToString(destinationCasted);
return update(destinationCasted, null, true, theRequest);
}
private boolean isDeleted(BaseHasResource entityToUpdate) {
return entityToUpdate.getDeleted() != null;
}
@ -1209,7 +1151,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw createResourceGoneException(entity.get());
}
T retVal = toResource(myResourceType, entity.get(), null, false);
T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity.get(), null, false);
ourLog.debug("Processed read on {} in {}ms", jpaPid, w.getMillis());
return retVal;
@ -1243,7 +1185,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
BaseHasResource entity = readEntity(theId, theRequest);
validateResourceType(entity);
T retVal = toResource(myResourceType, entity, null, false);
T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false);
if (theDeletedOk == false) {
if (isDeleted(entity)) {
@ -1297,7 +1239,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entity = entityOpt.get();
try {
T resource = (T) toResource(entity, false);
T resource = (T) myJpaStorageResourceParser.toResource(entity, false);
reindex(resource, entity);
} catch (BaseServerResponseException | DataFormatException e) {
myResourceTableDao.updateIndexStatus(entity.getId(), INDEX_STATUS_INDEXING_FAILED);
@ -1379,6 +1321,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return entity;
}
@Override
protected IBasePersistedResource readEntityLatestVersion(ResourcePersistentId thePersistentId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
return myEntityManager.find(ResourceTable.class, thePersistentId.getIdAsLong());
}
@Override
@Nonnull
protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(theRequestDetails, getResourceName(), theId);
@ -1695,8 +1644,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
StopWatch w = new StopWatch();
T resource = theResource;
preProcessResourceForStorage(resource);
@ -1705,6 +1652,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
ResourceTable entity = null;
IIdType resourceId;
RestOperationTypeEnum update = RestOperationTypeEnum.UPDATE;
if (isNotBlank(theMatchUrl)) {
Set<JpaPid> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest, theResource)
.stream().map(id -> (JpaPid) id).collect(Collectors.toSet());
@ -1716,7 +1664,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity = myEntityManager.find(ResourceTable.class, pid.getId());
resourceId = entity.getIdDt();
} else {
DaoMethodOutcome outcome = create(resource, null, thePerformIndexing, theTransactionDetails, theRequest);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName());
DaoMethodOutcome outcome = doCreateForPostOrPut(theRequest, resource, theMatchUrl, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails);
// Pre-cache the match URL
if (outcome.getPersistentId() != null) {
@ -1755,86 +1704,17 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
if (create) {
return doCreateForPostOrPut(resource, null, thePerformIndexing, theTransactionDetails, theRequest, requestPartitionId);
return doCreateForPostOrPut(theRequest, resource, null, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails);
}
}
if (resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) != entity.getVersion()) {
throw new ResourceVersionConflictException(Msg.code(989) + "Trying to update " + resourceId + " but this is not the current version");
}
// Start
if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) {
throw new UnprocessableEntityException(Msg.code(990) + "Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]");
}
IBaseResource oldResource;
if (getConfig().isMassIngestionMode()) {
oldResource = null;
} else {
oldResource = toResource(entity, false);
}
/*
* Mark the entity as not deleted - This is also done in the actual updateInternal()
* method later on so it usually doesn't matter whether we do it here, but in the
* case of a transaction with multiple PUTs we don't get there until later so
* having this here means that a transaction can have a reference in one
* resource to another resource in the same transaction that is being
* un-deleted by the transaction. Wacky use case, sure. But it's real.
*
* See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources
* for a test that needs this.
*/
boolean wasDeleted = isDeleted(entity);
entity.setDeleted(null);
/*
* If we aren't indexing, that means we're doing this inside a transaction.
* The transaction will do the actual storage to the database a bit later on,
* after placeholder IDs have been replaced, by calling {@link #updateInternal}
* directly. So we just bail now.
*/
if (!thePerformIndexing) {
resource.setId(entity.getIdDt().getValue());
DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted);
outcome.setPreviousResource(oldResource);
if (!outcome.isNop()) {
// Technically this may not end up being right since we might not increment if the
// contents turn out to be the same
outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1)));
}
return outcome;
}
/*
* Otherwise, we're not in a transaction
*/
ResourceTable savedEntity = updateInternal(theRequest, resource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails);
if (thePerformIndexing) {
Collection<? extends BaseTag> tagList = Collections.emptyList();
if (entity.isHasTags()) {
tagList = entity.getTags();
}
long version = entity.getVersion();
populateResourceMetadata(entity, false, tagList, version, getResourceType(), resource);
}
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, resource).setCreated(wasDeleted);
if (!thePerformIndexing) {
IIdType id = getContext().getVersion().newIdType();
id.setValue(entity.getIdDt().getValue());
outcome.setId(id);
}
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
ourLog.debug(msg);
return outcome;
return doUpdateForUpdateOrPatch(theRequest, resourceId, theMatchUrl, thePerformIndexing, theForceUpdateVersion, resource, entity, update, theTransactionDetails);
}
/**
* Method for updating the historical version of the resource when a history version id is included in the request.
*
@ -1849,8 +1729,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// No need for indexing as this will update a non-current version of the resource which will not be searchable
preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, false);
BaseHasResource entity = null;
BaseHasResource currentEntity = null;
BaseHasResource entity;
BaseHasResource currentEntity;
IIdType resourceId;
@ -1879,12 +1759,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
entity.setDeleted(null);
boolean isUpdatingCurrent = resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) == currentEntity.getVersion();
IBasePersistedResource savedEntity = updateHistoryEntity(theRequest, theResource, currentEntity, entity, resourceId, theTransactionDetails, isUpdatingCurrent);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted);
DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource, null, RestOperationTypeEnum.UPDATE).setCreated(wasDeleted);
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart());
outcome.setOperationOutcome(createInfoOperationOutcome(msg));
populateOperationOutcomeForUpdate(w, outcome, null, RestOperationTypeEnum.UPDATE);
ourLog.debug(msg);
return outcome;
}

View File

@ -1,13 +1,21 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.util.QueryChunker;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
@ -26,6 +34,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@ -59,25 +70,36 @@ import java.util.stream.Collectors;
* #L%
*/
public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends BaseHapiFhirDao<IBaseResource> implements IFhirSystemDao<T, MT> {
public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends BaseStorageDao implements IFhirSystemDao<T, MT> {
public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0];
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirSystemDao.class);
public ResourceCountCache myResourceCountsCache;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
private TransactionProcessor myTransactionProcessor;
@Autowired
private ApplicationContext myApplicationContext;
@Autowired
private ExpungeService myExpungeService;
@Autowired
private IResourceTableDao myResourceTableDao;
@Autowired
private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory;
@Autowired
private IResourceTagDao myResourceTagDao;
@Autowired
private IInterceptorBroadcaster myInterceptorBroadcaster;
@VisibleForTesting
public void setTransactionProcessorForUnitTest(TransactionProcessor theTransactionProcessor) {
myTransactionProcessor = theTransactionProcessor;
}
@Override
@PostConstruct
public void start() {
super.start();
myTransactionProcessor.setDao(this);
}
@ -125,7 +147,7 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
@Override
public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) {
StopWatch w = new StopWatch();
IBundleProvider retVal = super.history(theRequestDetails, null, null, theSince, theUntil, theOffset);
IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, null, null, theSince, theUntil, theOffset);
ourLog.info("Processed global history in {}ms", w.getMillisAndRestart());
return retVal;
}
@ -260,4 +282,25 @@ public abstract class BaseHapiFhirSystemDao<T extends IBaseBundle, MT> extends B
return null;
}
@Override
protected IInterceptorBroadcaster getInterceptorBroadcaster() {
return myInterceptorBroadcaster;
}
@Override
protected DaoConfig getConfig() {
return myDaoConfig;
}
@Override
public FhirContext getContext() {
return myFhirContext;
}
@VisibleForTesting
public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig;
}
}

View File

@ -1,42 +0,0 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.model.dstu2.resource.Composition;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.http.HttpServletRequest;
public class FhirResourceDaoCompositionDstu2 extends BaseHapiFhirResourceDao<Composition>implements IFhirResourceDaoComposition<Composition> {
@Override
public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, IPrimitiveType<Integer> theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) {
throw new NotImplementedOperationException(Msg.code(955) + "$document not implemented in DSTU2");
}
}

View File

@ -1,89 +0,0 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import com.google.common.collect.Lists;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_40;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_40;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private FhirContext myDstu2Hl7OrgContext = FhirContext.forDstu2Hl7Org();
protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) {
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
String expression = theResource != null ? theResource.getXpath() : null;
List<String> base = theResource != null ? Lists.newArrayList(theResource.getBase()) : null;
requestReindexForRelatedResources(reindex, base, theRequestDetails);
}
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postPersist(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postUpdate(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails);
reindexAffectedResources(theResourceToDelete, theRequestDetails);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
String encoded = getContext().newJsonParser().encodeResourceToString(theResource);
org.hl7.fhir.dstu2.model.SearchParameter hl7Org = myDstu2Hl7OrgContext.newJsonParser().parseResource(org.hl7.fhir.dstu2.model.SearchParameter.class, encoded);
org.hl7.fhir.r4.model.SearchParameter convertedSp = (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_10_40.convertResource(hl7Org, new BaseAdvisor_10_40(false));
if (isBlank(convertedSp.getExpression()) && isNotBlank(hl7Org.getXpath())) {
convertedSp.setExpression(hl7Org.getXpath());
}
FhirResourceDaoSearchParameterR4.validateSearchParam(convertedSp, getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor);
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.jpa.model.entity.TagDefinition;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;

View File

@ -0,0 +1,52 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.jpa.model.entity.BaseTag;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nullable;
import java.util.Collection;
public interface IJpaStorageResourceParser extends IStorageResourceParser {
/**
* Convert a storage entity into a FHIR resource model instance
*/
<R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation);
/**
* Populate the metadata (Resource.meta.*) from a storage entity and other related
* objects pulled from the database
*/
<R extends IBaseResource> R populateResourceMetadata(IBaseResourceEntity theEntitySource, boolean theForHistoryOperation, @Nullable Collection<? extends BaseTag> tagList, long theVersion, R theResourceTarget);
/**
* Populates a resource model object's metadata (Resource.meta.*) based on the
* values from a stroage entity.
*
* @param theEntitySource The source
* @param theResourceTarget The target
*/
void updateResourceMetadata(IBaseResourceEntity theEntitySource, IBaseResource theResourceTarget);
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.dao.r4;
package ca.uhn.fhir.jpa.dao;
/*
* #%L
@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.dao.r4;
*/
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.SortSpec;
@ -29,14 +28,14 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
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 org.hl7.fhir.r4.model.Composition;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
public class FhirResourceDaoCompositionR4 extends BaseHapiFhirResourceDao<Composition> implements IFhirResourceDaoComposition<Composition> {
public class JpaResourceDaoComposition<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoComposition<T> {
@Override
public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, IPrimitiveType<Integer> theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) {

View File

@ -29,29 +29,70 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.SortOrderEnum;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionTemplate;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
public class JpaResourceDaoObservation<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoObservation<T> {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc;
@Autowired
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}
private String getEffectiveParamName() {
return Observation.SP_DATE;
}
private String getCodeParamName() {
return Observation.SP_CODE;
}
private String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
private String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity,
Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
@ -130,12 +171,4 @@ public abstract class BaseHapiFhirResourceDaoObservation<T extends IBaseResource
}
abstract protected String getEffectiveParamName();
abstract protected String getCodeParamName();
abstract protected String getSubjectParamName();
abstract protected String getPatientParamName();
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.dao.r4;
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@ -6,18 +6,18 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.util.ElementUtil;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@ -46,60 +46,63 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
* #L%
*/
public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
public class JpaResourceDaoSearchParameter<T extends IBaseResource> extends BaseHapiFhirResourceDao<T> implements IFhirResourceDaoSearchParameter<T> {
private static final Pattern REGEX_SP_EXPRESSION_HAS_PATH = Pattern.compile("[( ]*([A-Z][a-zA-Z]+\\.)?[a-z].*");
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
private VersionCanonicalizer myVersionCanonicalizer;
protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) {
protected void reindexAffectedResources(T theResource, RequestDetails theRequestDetails) {
// N.B. Don't do this on the canonicalized version
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
String expression = theResource != null ? theResource.getExpression() : null;
List<String> base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null;
org.hl7.fhir.r5.model.SearchParameter searchParameter = myVersionCanonicalizer.searchParameterToCanonical(theResource);
List<String> base = theResource != null ? searchParameter.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null;
requestReindexForRelatedResources(reindex, base, theRequestDetails);
}
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
protected void postPersist(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) {
super.postPersist(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
protected void postUpdate(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) {
super.postUpdate(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
protected void preDelete(T theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails);
reindexAffectedResources(theResourceToDelete, theRequestDetails);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
validateSearchParam(theResource, getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor);
validateSearchParam(theResource, getContext(), getConfig());
}
public static void validateSearchParam(SearchParameter theResource, FhirContext theContext, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry, ISearchParamExtractor theSearchParamExtractor) {
public void validateSearchParam(IBaseResource theResource, FhirContext theContext, DaoConfig theDaoConfig) {
org.hl7.fhir.r5.model.SearchParameter searchParameter = myVersionCanonicalizer.searchParameterToCanonical(theResource);
/*
* If overriding built-in SPs is disabled on this server, make sure we aren't
* doing that
*/
if (theDaoConfig.getModelConfig().isDefaultSearchParamsCanBeOverridden() == false) {
for (IPrimitiveType<?> nextBaseType : theResource.getBase()) {
for (IPrimitiveType<?> nextBaseType : searchParameter.getBase()) {
String nextBase = nextBaseType.getValueAsString();
RuntimeSearchParam existingSearchParam = theSearchParamRegistry.getActiveSearchParam(nextBase, theResource.getCode());
RuntimeSearchParam existingSearchParam = mySearchParamRegistry.getActiveSearchParam(nextBase, searchParameter.getCode());
if (existingSearchParam != null) {
boolean isBuiltIn = existingSearchParam.getId() == null;
isBuiltIn |= existingSearchParam.getUri().startsWith("http://hl7.org/fhir/SearchParameter/");
if (isBuiltIn) {
throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + theResource.getCode() + " because overriding is disabled on this server");
throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + searchParameter.getCode() + " because overriding is disabled on this server");
}
}
}
@ -109,34 +112,34 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
* Everything below is validating that the SP is actually valid. We'll only do that if the
* SPO is active, so that we don't block people from uploading works-in-progress
*/
if (theResource.getStatus() == null) {
if (searchParameter.getStatus() == null) {
throw new UnprocessableEntityException(Msg.code(1112) + "SearchParameter.status is missing or invalid");
}
if (!theResource.getStatus().name().equals("ACTIVE")) {
if (!searchParameter.getStatus().name().equals("ACTIVE")) {
return;
}
if (ElementUtil.isEmpty(theResource.getBase()) && (theResource.getType() == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(theResource.getType().name()))) {
if (ElementUtil.isEmpty(searchParameter.getBase()) && (searchParameter.getType() == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(searchParameter.getType().name()))) {
throw new UnprocessableEntityException(Msg.code(1113) + "SearchParameter.base is missing");
}
boolean isUnique = hasAnyExtensionUniqueSetTo(theResource, true);
boolean isUnique = hasAnyExtensionUniqueSetTo(searchParameter, true);
if (theResource.getType() != null && theResource.getType().name().equals(Enumerations.SearchParamType.COMPOSITE.name()) && isBlank(theResource.getExpression())) {
if (searchParameter.getType() != null && searchParameter.getType().name().equals(Enumerations.SearchParamType.COMPOSITE.name()) && isBlank(searchParameter.getExpression())) {
// this is ok
} else if (isBlank(theResource.getExpression())) {
} else if (isBlank(searchParameter.getExpression())) {
throw new UnprocessableEntityException(Msg.code(1114) + "SearchParameter.expression is missing");
} else {
if (isUnique) {
if (theResource.getComponent().size() == 0) {
if (searchParameter.getComponent().size() == 0) {
throw new UnprocessableEntityException(Msg.code(1115) + "SearchParameter is marked as unique but has no components");
}
for (SearchParameter.SearchParameterComponentComponent next : theResource.getComponent()) {
for (SearchParameter.SearchParameterComponentComponent next : searchParameter.getComponent()) {
if (isBlank(next.getDefinition())) {
throw new UnprocessableEntityException(Msg.code(1116) + "SearchParameter is marked as unique but is missing component.definition");
}
@ -146,14 +149,13 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
FhirVersionEnum fhirVersion = theContext.getVersion().getVersion();
if (fhirVersion.isOlderThan(FhirVersionEnum.DSTU3)) {
// omitting validation for DSTU2_HL7ORG, DSTU2_1 and DSTU2
}
else {
} else {
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
validateExpressionPath(theResource);
validateExpressionPath(searchParameter);
String expression = getExpression(theResource);
String expression = getExpression(searchParameter);
try {
theContext.newFhirPath().parse(expression);
@ -166,7 +168,12 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
}
private static void validateExpressionPath(SearchParameter theSearchParameter){
@VisibleForTesting
void setVersionCanonicalizerForUnitTest(VersionCanonicalizer theVersionCanonicalizer) {
myVersionCanonicalizer = theVersionCanonicalizer;
}
private static void validateExpressionPath(SearchParameter theSearchParameter) {
String expression = getExpression(theSearchParameter);
boolean isResourceOfTypeComposite = theSearchParameter.getType() == Enumerations.SearchParamType.COMPOSITE;
@ -175,23 +182,23 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao<Se
boolean isUnique = hasAnyExtensionUniqueSetTo(theSearchParameter, true);
if ( !isUnique && !isResourceOfTypeComposite && !isResourceOfTypeSpecial && !expressionHasPath ) {
if (!isUnique && !isResourceOfTypeComposite && !isResourceOfTypeSpecial && !expressionHasPath) {
throw new UnprocessableEntityException(Msg.code(1120) + "SearchParameter.expression value \"" + expression + "\" is invalid due to missing/incorrect path");
}
}
private static String getExpression(SearchParameter theSearchParameter){
private static String getExpression(SearchParameter theSearchParameter) {
return theSearchParameter.getExpression().trim();
}
private static boolean hasAnyExtensionUniqueSetTo(SearchParameter theSearchParameter, boolean theValue){
private static boolean hasAnyExtensionUniqueSetTo(SearchParameter theSearchParameter, boolean theValue) {
String theValueAsString = Boolean.toString(theValue);
return theSearchParameter
.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE)
.stream()
.anyMatch(t-> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
.anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString()));
}
}

View File

@ -0,0 +1,490 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.entity.PartitionEntity;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.BaseTag;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
import ca.uhn.fhir.model.api.IResource;
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.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.util.MetaUtil;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.cleanProvenanceSourceUri;
import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.decodeResource;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class JpaStorageResourceParser implements IJpaStorageResourceParser {
public static final LenientErrorHandler LENIENT_ERROR_HANDLER = new LenientErrorHandler(false).setErrorOnInvalidValue(false);
private static final Logger ourLog = LoggerFactory.getLogger(JpaStorageResourceParser.class);
@Autowired
private FhirContext myContext;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private IResourceHistoryTableDao myResourceHistoryTableDao;
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private IPartitionLookupSvc myPartitionLookupSvc;
@Override
public IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
Class<? extends IBaseResource> resourceType = type.getImplementingClass();
return toResource(resourceType, (IBaseResourceEntity) theEntity, null, theForHistoryOperation);
}
@Override
public <R extends IBaseResource> R toResource(Class<R> theResourceType, IBaseResourceEntity theEntity, Collection<ResourceTag> theTagList, boolean theForHistoryOperation) {
// 1. get resource, it's encoding and the tags if any
byte[] resourceBytes;
String resourceText;
ResourceEncodingEnum resourceEncoding;
@Nullable
Collection<? extends BaseTag> tagList = Collections.emptyList();
long version;
String provenanceSourceUri = null;
String provenanceRequestId = null;
if (theEntity instanceof ResourceHistoryTable) {
ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
resourceBytes = history.getResource();
resourceText = history.getResourceTextVc();
resourceEncoding = history.getEncoding();
switch (myDaoConfig.getTagStorageMode()) {
case VERSIONED:
default:
if (history.isHasTags()) {
tagList = history.getTags();
}
break;
case NON_VERSIONED:
if (history.getResourceTable().isHasTags()) {
tagList = history.getResourceTable().getTags();
}
break;
case INLINE:
tagList = null;
}
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
provenanceSourceUri = history.getProvenance().getSourceUri();
}
} else if (theEntity instanceof ResourceTable) {
ResourceTable resource = (ResourceTable) theEntity;
ResourceHistoryTable history;
if (resource.getCurrentVersionEntity() != null) {
history = resource.getCurrentVersionEntity();
} else {
version = theEntity.getVersion();
history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
((ResourceTable) theEntity).setCurrentVersionEntity(history);
while (history == null) {
if (version > 1L) {
version--;
history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version);
} else {
return null;
}
}
}
resourceBytes = history.getResource();
resourceEncoding = history.getEncoding();
resourceText = history.getResourceTextVc();
switch (myDaoConfig.getTagStorageMode()) {
case VERSIONED:
case NON_VERSIONED:
if (resource.isHasTags()) {
tagList = resource.getTags();
}
break;
case INLINE:
tagList = null;
break;
}
version = history.getVersion();
if (history.getProvenance() != null) {
provenanceRequestId = history.getProvenance().getRequestId();
provenanceSourceUri = history.getProvenance().getSourceUri();
}
} else if (theEntity instanceof ResourceSearchView) {
// This is the search View
ResourceSearchView view = (ResourceSearchView) theEntity;
resourceBytes = view.getResource();
resourceText = view.getResourceTextVc();
resourceEncoding = view.getEncoding();
version = view.getVersion();
provenanceRequestId = view.getProvenanceRequestId();
provenanceSourceUri = view.getProvenanceSourceUri();
switch (myDaoConfig.getTagStorageMode()) {
case VERSIONED:
case NON_VERSIONED:
if (theTagList != null) {
tagList = theTagList;
}
break;
case INLINE:
tagList = null;
break;
}
} else {
// something wrong
return null;
}
// 2. get The text
String decodedResourceText = decodedResourceText(resourceBytes, resourceText, resourceEncoding);
// 3. Use the appropriate custom type if one is specified in the context
Class<R> resourceType = determineTypeToParse(theResourceType, tagList);
// 4. parse the text to FHIR
R retVal = parseResource(theEntity, resourceEncoding, decodedResourceText, resourceType);
// 5. fill MetaData
retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, retVal);
// 6. Handle source (provenance)
populateResourceSource(provenanceSourceUri, provenanceRequestId, retVal);
// 7. Add partition information
populateResourcePartitionInformation(theEntity, retVal);
return retVal;
}
private <R extends IBaseResource> void populateResourcePartitionInformation(IBaseResourceEntity theEntity, R retVal) {
if (myPartitionSettings.isPartitioningEnabled()) {
PartitionablePartitionId partitionId = theEntity.getPartitionId();
if (partitionId != null && partitionId.getPartitionId() != null) {
PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId());
retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId());
} else {
retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null);
}
}
}
private <R extends IBaseResource> void populateResourceSource(String provenanceSourceUri, String provenanceRequestId, R retVal) {
if (isNotBlank(provenanceRequestId) || isNotBlank(provenanceSourceUri)) {
String sourceString = cleanProvenanceSourceUri(provenanceSourceUri)
+ (isNotBlank(provenanceRequestId) ? "#" : "")
+ defaultString(provenanceRequestId);
MetaUtil.setSource(myContext, retVal, sourceString);
}
}
@SuppressWarnings("unchecked")
private <R extends IBaseResource> R parseResource(IBaseResourceEntity theEntity, ResourceEncodingEnum resourceEncoding, String decodedResourceText, Class<R> resourceType) {
R retVal;
if (resourceEncoding != ResourceEncodingEnum.DEL) {
IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getId());
try {
retVal = parser.parseResource(resourceType, decodedResourceText);
} catch (Exception e) {
StringBuilder b = new StringBuilder();
b.append("Failed to parse database resource[");
b.append(myContext.getResourceType(resourceType));
b.append("/");
b.append(theEntity.getIdDt().getIdPart());
b.append(" (pid ");
b.append(theEntity.getId());
b.append(", version ");
b.append(theEntity.getFhirVersion().name());
b.append("): ");
b.append(e.getMessage());
String msg = b.toString();
ourLog.error(msg, e);
throw new DataFormatException(Msg.code(928) + msg, e);
}
} else {
retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance();
}
return retVal;
}
@SuppressWarnings("unchecked")
private <R extends IBaseResource> Class<R> determineTypeToParse(Class<R> theResourceType, @Nullable Collection<? extends BaseTag> tagList) {
Class<R> resourceType = theResourceType;
if (tagList != null) {
if (myContext.hasDefaultTypeForProfile()) {
for (BaseTag nextTag : tagList) {
if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
String profile = nextTag.getTag().getCode();
if (isNotBlank(profile)) {
Class<? extends IBaseResource> newType = myContext.getDefaultTypeForProfile(profile);
if (newType != null && theResourceType.isAssignableFrom(newType)) {
ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile);
resourceType = (Class<R>) newType;
break;
}
}
}
}
}
}
return resourceType;
}
@SuppressWarnings("unchecked")
@Override
public <R extends IBaseResource> R populateResourceMetadata(IBaseResourceEntity theEntitySource, boolean theForHistoryOperation, @Nullable Collection<? extends BaseTag> tagList, long theVersion, R theResourceTarget) {
if (theResourceTarget instanceof IResource) {
IResource res = (IResource) theResourceTarget;
theResourceTarget = (R) populateResourceMetadataHapi(theEntitySource, tagList, theForHistoryOperation, res, theVersion);
} else {
IAnyResource res = (IAnyResource) theResourceTarget;
theResourceTarget = populateResourceMetadataRi(theEntitySource, tagList, theForHistoryOperation, res, theVersion);
}
return theResourceTarget;
}
@SuppressWarnings("unchecked")
private <R extends IResource> R populateResourceMetadataHapi(IBaseResourceEntity theEntity, @Nullable Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, R res, Long theVersion) {
R retVal = res;
if (theEntity.getDeleted() != null) {
res = (R) myContext.getResourceDefinition(res).newInstance();
retVal = res;
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
if (theForHistoryOperation) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
}
} else if (theForHistoryOperation) {
/*
* If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
*/
Date published = theEntity.getPublished().getValue();
Date updated = theEntity.getUpdated().getValue();
if (published.equals(updated)) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
} else {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
}
}
res.setId(theEntity.getIdDt().withVersion(theVersion.toString()));
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
if (theTagList != null) {
if (theEntity.isHasTags()) {
TagList tagList = new TagList();
List<IBaseCoding> securityLabels = new ArrayList<>();
List<IdDt> profiles = new ArrayList<>();
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
case PROFILE:
profiles.add(new IdDt(next.getTag().getCode()));
break;
case SECURITY_LABEL:
IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt();
secLabel.setSystem(next.getTag().getSystem());
secLabel.setCode(next.getTag().getCode());
secLabel.setDisplay(next.getTag().getDisplay());
securityLabels.add(secLabel);
break;
case TAG:
tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay()));
break;
}
}
if (tagList.size() > 0) {
ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList);
}
if (securityLabels.size() > 0) {
ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels));
}
if (profiles.size() > 0) {
ResourceMetadataKeyEnum.PROFILES.put(res, profiles);
}
}
}
return retVal;
}
@SuppressWarnings("unchecked")
private <R extends IBaseResource> R populateResourceMetadataRi(IBaseResourceEntity theEntity, @Nullable Collection<? extends BaseTag> theTagList, boolean theForHistoryOperation, IAnyResource res, Long theVersion) {
R retVal = (R) res;
if (theEntity.getDeleted() != null) {
res = (IAnyResource) myContext.getResourceDefinition(res).newInstance();
retVal = (R) res;
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
if (theForHistoryOperation) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.DELETE.toCode());
}
} else if (theForHistoryOperation) {
/*
* If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
*/
Date published = theEntity.getPublished().getValue();
Date updated = theEntity.getUpdated().getValue();
if (published.equals(updated)) {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.POST.toCode());
} else {
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.PUT.toCode());
}
}
res.getMeta().setLastUpdated(null);
res.getMeta().setVersionId(null);
updateResourceMetadata(theEntity, res);
res.setId(res.getIdElement().withVersion(theVersion.toString()));
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
if (theTagList != null) {
res.getMeta().getTag().clear();
res.getMeta().getProfile().clear();
res.getMeta().getSecurity().clear();
for (BaseTag next : theTagList) {
switch (next.getTag().getTagType()) {
case PROFILE:
res.getMeta().addProfile(next.getTag().getCode());
break;
case SECURITY_LABEL:
IBaseCoding sec = res.getMeta().addSecurity();
sec.setSystem(next.getTag().getSystem());
sec.setCode(next.getTag().getCode());
sec.setDisplay(next.getTag().getDisplay());
break;
case TAG:
IBaseCoding tag = res.getMeta().addTag();
tag.setSystem(next.getTag().getSystem());
tag.setCode(next.getTag().getCode());
tag.setDisplay(next.getTag().getDisplay());
break;
}
}
}
return retVal;
}
@Override
public void updateResourceMetadata(IBaseResourceEntity theEntitySource, IBaseResource theResourceTarget) {
IIdType id = theEntitySource.getIdDt();
if (myContext.getVersion().getVersion().isRi()) {
id = myContext.getVersion().newIdType().setValue(id.getValue());
}
if (id.hasResourceType() == false) {
id = id.withResourceType(theEntitySource.getResourceType());
}
theResourceTarget.setId(id);
if (theResourceTarget instanceof IResource) {
ResourceMetadataKeyEnum.VERSION.put((IResource) theResourceTarget, id.getVersionIdPart());
ResourceMetadataKeyEnum.UPDATED.put((IResource) theResourceTarget, theEntitySource.getUpdated());
} else {
IBaseMetaType meta = theResourceTarget.getMeta();
meta.setVersionId(id.getVersionIdPart());
meta.setLastUpdated(theEntitySource.getUpdatedDate());
}
}
private FhirContext getContext(FhirVersionEnum theVersion) {
Validate.notNull(theVersion, "theVersion must not be null");
if (theVersion == myContext.getVersion().getVersion()) {
return myContext;
}
return FhirContext.forCached(theVersion);
}
private static String decodedResourceText(byte[] resourceBytes, String resourceText, ResourceEncodingEnum resourceEncoding) {
String decodedResourceText;
if (resourceText != null) {
decodedResourceText = resourceText;
} else {
decodedResourceText = decodeResource(resourceBytes, resourceEncoding);
}
return decodedResourceText;
}
private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
for (IBaseCoding next : theSecurityLabels) {
retVal.add((BaseCodingDt) next);
}
return retVal;
}
}

View File

@ -1,59 +0,0 @@
package ca.uhn.fhir.jpa.dao.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.jpa.api.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
import org.hl7.fhir.dstu3.model.Composition;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
public class FhirResourceDaoCompositionDstu3 extends BaseHapiFhirResourceDao<Composition> implements IFhirResourceDaoComposition<Composition> {
@Override
public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, IPrimitiveType<Integer> theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theOffset != null) {
paramMap.setOffset(theOffset.getValue());
}
paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive()));
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdate);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
return search(paramMap, theRequestDetails);
}
}

View File

@ -1,83 +0,0 @@
package ca.uhn.fhir.jpa.dao.dstu3;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObservation<Observation> {
@Autowired
private IRequestPartitionHelperSvc myPartitionHelperSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}
@Override
protected String getEffectiveParamName() {
return Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -1,80 +0,0 @@
package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_40;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40;
import org.hl7.fhir.dstu3.model.PrimitiveType;
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.stream.Collectors;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class FhirResourceDaoSearchParameterDstu3 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) {
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
String expression = theResource != null ? theResource.getExpression() : null;
List<String> base = theResource != null ? theResource.getBase().stream().map(PrimitiveType::asStringValue).collect(Collectors.toList()) : null;
requestReindexForRelatedResources(reindex, base, theRequestDetails);
}
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postPersist(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postUpdate(theEntity, theResource, theRequestDetails);
reindexAffectedResources(theResource, theRequestDetails);
}
@Override
protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails);
reindexAffectedResources(theResourceToDelete, theRequestDetails);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
org.hl7.fhir.r4.model.SearchParameter resource = (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_30_40.convertResource(theResource, new BaseAdvisor_30_40(false));
FhirResourceDaoSearchParameterR4.validateSearchParam(
resource,
getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor);
}
}

View File

@ -25,10 +25,10 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
@ -42,10 +42,10 @@ import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
@ -124,11 +124,13 @@ public class ResourceExpungeService implements IResourceExpungeService {
private DaoConfig myDaoConfig;
@Autowired
private MemoryCacheService myMemoryCacheService;
@Autowired
private IJpaStorageResourceParser myJpaStorageResourceParser;
@Override
@Transactional
public List<ResourcePersistentId> findHistoricalVersionsOfNonDeletedResources(String theResourceName, ResourcePersistentId theResourceId, int theRemainingCount) {
if(isEmptyQuery(theRemainingCount)){
if (isEmptyQuery(theRemainingCount)) {
return Collections.EMPTY_LIST;
}
@ -156,7 +158,7 @@ public class ResourceExpungeService implements IResourceExpungeService {
@Override
@Transactional
public List<ResourcePersistentId> findHistoricalVersionsOfDeletedResources(String theResourceName, ResourcePersistentId theResourceId, int theRemainingCount) {
if(isEmptyQuery(theRemainingCount)){
if (isEmptyQuery(theRemainingCount)) {
return Collections.EMPTY_LIST;
}
@ -194,7 +196,7 @@ public class ResourceExpungeService implements IResourceExpungeService {
* be optimized, but expunge is hopefully not frequently called on busy servers
* so it shouldn't be too big a deal.
*/
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
myMemoryCacheService.invalidateAllCaches();
@ -222,8 +224,7 @@ public class ResourceExpungeService implements IResourceExpungeService {
private void callHooks(RequestDetails theRequestDetails, AtomicInteger theRemainingCount, ResourceHistoryTable theVersion, IdDt theId) {
final AtomicInteger counter = new AtomicInteger();
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myInterceptorBroadcaster, theRequestDetails)) {
IFhirResourceDao<?> resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType());
IBaseResource resource = resourceDao.toResource(theVersion, false);
IBaseResource resource = myJpaStorageResourceParser.toResource(theVersion, false);
HookParams params = new HookParams()
.add(AtomicInteger.class, counter)
.add(IIdType.class, theId)
@ -327,7 +328,7 @@ public class ResourceExpungeService implements IResourceExpungeService {
private void expungeHistoricalVersionsOfId(RequestDetails theRequestDetails, Long myResourceId, AtomicInteger theRemainingCount) {
Pageable page;
synchronized (theRemainingCount){
synchronized (theRemainingCount) {
if (expungeLimitReached(theRemainingCount)) {
return;
}
@ -351,7 +352,7 @@ public class ResourceExpungeService implements IResourceExpungeService {
return new SliceImpl<>(Collections.singletonList(myVersion.getId()));
}
private boolean isEmptyQuery(int theCount){
private boolean isEmptyQuery(int theCount) {
return theCount <= 0;
}

View File

@ -1,88 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObservation<Observation> {
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired
private IRequestPartitionHelperSvc myRequestPartitionHelperService;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}
@Override
protected String getEffectiveParamName() {
return Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -1,59 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4b;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.jpa.api.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
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 org.hl7.fhir.r4b.model.Composition;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
public class FhirResourceDaoCompositionR4B extends BaseHapiFhirResourceDao<Composition> implements IFhirResourceDaoComposition<Composition> {
@Override
public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, IPrimitiveType<Integer> theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theOffset != null) {
paramMap.setOffset(theOffset.getValue());
}
paramMap.setIncludes(Collections.singleton(IBaseResource.INCLUDE_ALL.asRecursive()));
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdate);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
return search(paramMap, theRequestDetails);
}
}

View File

@ -1,83 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4b;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4b.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationR4B extends BaseHapiFhirResourceDaoObservation<Observation> {
@Autowired
private IRequestPartitionHelperSvc myPartitionHelperSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}
@Override
protected String getEffectiveParamName() {
return org.hl7.fhir.r4.model.Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return org.hl7.fhir.r4.model.Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return org.hl7.fhir.r4.model.Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return org.hl7.fhir.r4.model.Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -1,83 +0,0 @@
package ca.uhn.fhir.jpa.dao.r4b;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50;
import org.hl7.fhir.r4b.model.CodeType;
import org.hl7.fhir.r4b.model.SearchParameter;
import org.hl7.fhir.r5.model.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.stream.Collectors;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class FhirResourceDaoSearchParameterR4B extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
protected void refactorAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) {
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
String expression = theResource != null ? theResource.getExpression() : null;
List<String> base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null;
requestReindexForRelatedResources(reindex, base, theRequestDetails);
}
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postPersist(theEntity, theResource, theRequestDetails);
refactorAffectedResources(theResource, theRequestDetails);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postUpdate(theEntity, theResource, theRequestDetails);
refactorAffectedResources(theResource, theRequestDetails);
}
@Override
protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails);
refactorAffectedResources(theResourceToDelete, theRequestDetails);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
Resource resR5 = VersionConvertorFactory_43_50.convertResource(theResource, new BaseAdvisor_43_50(false));
FhirResourceDaoSearchParameterR4.validateSearchParam(
(org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_40_50.convertResource(resR5, new BaseAdvisor_40_50(false)),
getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor);
}
}

View File

@ -1,59 +0,0 @@
package ca.uhn.fhir.jpa.dao.r5;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.jpa.api.dao.IFhirResourceDaoComposition;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringParam;
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 org.hl7.fhir.r5.model.Composition;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
public class FhirResourceDaoCompositionR5 extends BaseHapiFhirResourceDao<Composition> implements IFhirResourceDaoComposition<Composition> {
@Override
public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, IPrimitiveType<Integer> theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
}
if (theOffset != null) {
paramMap.setOffset(theOffset.getValue());
}
paramMap.setIncludes(Collections.singleton(IBaseResource.INCLUDE_ALL.asRecursive()));
paramMap.setSort(theSort);
paramMap.setLastUpdated(theLastUpdate);
if (theId != null) {
paramMap.add("_id", new StringParam(theId.getIdPart()));
}
return search(paramMap, theRequestDetails);
}
}

View File

@ -1,83 +0,0 @@
package ca.uhn.fhir.jpa.dao.r5;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* 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.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r5.model.Observation;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObservation<Observation> {
@Autowired
private IRequestPartitionHelperSvc myPartitionHelperSvc;
@Override
public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails);
RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null);
return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId);
}
@Override
protected String getEffectiveParamName() {
return org.hl7.fhir.r4.model.Observation.SP_DATE;
}
@Override
protected String getCodeParamName() {
return org.hl7.fhir.r4.model.Observation.SP_CODE;
}
@Override
protected String getSubjectParamName() {
return org.hl7.fhir.r4.model.Observation.SP_SUBJECT;
}
@Override
protected String getPatientParamName() {
return org.hl7.fhir.r4.model.Observation.SP_PATIENT;
}
@Override
public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) {
return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull,
thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate,
theCreateNewHistoryEntry);
}
}

View File

@ -1,79 +0,0 @@
package ca.uhn.fhir.jpa.dao.r5;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.SearchParameter;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.stream.Collectors;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
protected void refactorAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) {
Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null;
String expression = theResource != null ? theResource.getExpression() : null;
List<String> base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null;
requestReindexForRelatedResources(reindex, base, theRequestDetails);
}
@Override
protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postPersist(theEntity, theResource, theRequestDetails);
refactorAffectedResources(theResource, theRequestDetails);
}
@Override
protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) {
super.postUpdate(theEntity, theResource, theRequestDetails);
refactorAffectedResources(theResource, theRequestDetails);
}
@Override
protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) {
super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails);
refactorAffectedResources(theResourceToDelete, theRequestDetails);
}
@Override
protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) {
super.validateResourceForStorage(theResource, theEntityToSave);
FhirResourceDaoSearchParameterR4.validateSearchParam(
(org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_40_50.convertResource(theResource, new BaseAdvisor_40_50(false)),
getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor);
}
}

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
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;
@ -42,7 +43,7 @@ public class DeleteExpungeSvcImpl implements IDeleteExpungeSvc {
private final DeleteExpungeSqlBuilder myDeleteExpungeSqlBuilder;
private final IFulltextSearchSvc myFullTextSearchSvc;
public DeleteExpungeSvcImpl(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, IFulltextSearchSvc theFullTextSearchSvc) {
public DeleteExpungeSvcImpl(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, @Autowired(required = false) IFulltextSearchSvc theFullTextSearchSvc) {
myEntityManager = theEntityManager;
myDeleteExpungeSqlBuilder = theDeleteExpungeSqlBuilder;
myFullTextSearchSvc = theFullTextSearchSvc;

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.dao.HistoryBuilder;
import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
import ca.uhn.fhir.jpa.entity.Search;
@ -104,13 +105,15 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
private RequestPartitionHelperSvc myRequestPartitionHelperSvc;
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private MemoryCacheService myMemoryCacheService;
@Autowired
private IJpaStorageResourceParser myJpaStorageResourceParser;
/*
* Non autowired fields (will be different for every instance
* of this class, since it's a prototype
*/
@Autowired
private MemoryCacheService myMemoryCacheService;
private Search mySearchEntity;
private String myUuid;
private SearchCacheStatusEnum myCacheStatus;
@ -162,7 +165,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
resource = next;
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(next.getResourceType());
retVal.add(dao.toResource(resource, true));
retVal.add(myJpaStorageResourceParser.toResource(resource, true));
}

View File

@ -23,11 +23,20 @@ package ca.uhn.fhir.jpa.search;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.HistorySearchStyleEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import java.util.Date;
import java.util.UUID;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
public class PersistedJpaBundleProviderFactory {
@Autowired
@ -46,4 +55,28 @@ public class PersistedJpaBundleProviderFactory {
public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchTask theTask, ISearchBuilder theSearchBuilder) {
return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(JpaConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder);
}
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset) {
return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null);
}
public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType) {
String resourceName = defaultIfBlank(theResourceType, null);
Search search = new Search();
search.setOffset(theOffset);
search.setDeleted(false);
search.setCreated(new Date());
search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive);
search.setUuid(UUID.randomUUID().toString());
search.setResourceType(resourceName);
search.setResourceId(theResourcePid);
search.setSearchType(SearchTypeEnum.HISTORY);
search.setStatus(SearchStatusEnum.FINISHED);
search.setHistorySearchStyle(searchParameterType);
return newInstance(theRequest, search);
}
}

View File

@ -39,15 +39,16 @@ import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.dao.BaseStorageDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.IResultIterator;
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTagDao;
import ca.uhn.fhir.jpa.dao.search.ResourceNotFoundInIndexException;
import ca.uhn.fhir.jpa.entity.ResourceSearchView;
import ca.uhn.fhir.jpa.interceptor.JpaPreResourceAccessDetails;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceTag;
@ -145,20 +146,34 @@ public class SearchBuilder implements ISearchBuilder {
@Deprecated
public static final int MAXIMUM_PAGE_SIZE = SearchConstants.MAX_PAGE_SIZE;
public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50;
public static final String RESOURCE_ID_ALIAS = "resource_id";
public static final String RESOURCE_VERSION_ALIAS = "resource_version";
private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class);
private static final JpaPid NO_MORE = new JpaPid(-1L);
private static final String MY_TARGET_RESOURCE_PID = "myTargetResourcePid";
private static final String MY_SOURCE_RESOURCE_PID = "mySourceResourcePid";
private static final String MY_TARGET_RESOURCE_TYPE = "myTargetResourceType";
private static final String MY_SOURCE_RESOURCE_TYPE = "mySourceResourceType";
private static final String MY_TARGET_RESOURCE_VERSION = "myTargetResourceVersion";
public static final String RESOURCE_ID_ALIAS = "resource_id";
public static final String RESOURCE_VERSION_ALIAS = "resource_version";
public static boolean myUseMaxPageSize50ForTest = false;
protected final IInterceptorBroadcaster myInterceptorBroadcaster;
protected final IResourceTagDao myResourceTagDao;
private final String myResourceName;
private final Class<? extends IBaseResource> myResourceType;
private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory;
private final SqlObjectFactory mySqlBuilderFactory;
private final HibernatePropertiesProvider myDialectProvider;
private final ModelConfig myModelConfig;
private final ISearchParamRegistry mySearchParamRegistry;
private final PartitionSettings myPartitionSettings;
private final DaoRegistry myDaoRegistry;
private final IResourceSearchViewDao myResourceSearchViewDao;
private final FhirContext myContext;
private final IIdHelperService myIdHelperService;
private final DaoConfig myDaoConfig;
private final IDao myCallingDao;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
private List<ResourcePersistentId> myAlsoIncludePids;
private CriteriaBuilder myCriteriaBuilder;
private SearchParameterMap myParams;
@ -168,30 +183,12 @@ public class SearchBuilder implements ISearchBuilder {
private Set<ResourcePersistentId> myPidSet;
private boolean myHasNextIteratorQuery = false;
private RequestPartitionId myRequestPartitionId;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
@Autowired(required = false)
private IFulltextSearchSvc myFulltextSearchSvc;
@Autowired(required = false)
private IElasticsearchSvc myIElasticsearchSvc;
private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory;
private final SqlObjectFactory mySqlBuilderFactory;
private final HibernatePropertiesProvider myDialectProvider;
private final ModelConfig myModelConfig;
private final ISearchParamRegistry mySearchParamRegistry;
private final PartitionSettings myPartitionSettings;
protected final IInterceptorBroadcaster myInterceptorBroadcaster;
protected final IResourceTagDao myResourceTagDao;
private final DaoRegistry myDaoRegistry;
private final IResourceSearchViewDao myResourceSearchViewDao;
private final FhirContext myContext;
private final IIdHelperService myIdHelperService;
private final DaoConfig myDaoConfig;
private final IDao myCallingDao;
@Autowired
private IJpaStorageResourceParser myJpaStorageResourceParser;
/**
* Constructor
@ -894,7 +891,7 @@ public class SearchBuilder implements ISearchBuilder {
IBaseResource resource = null;
if (next != null) {
resource = myCallingDao.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation);
resource = myJpaStorageResourceParser.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation);
}
if (resource == null) {
ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion());

View File

@ -38,7 +38,7 @@ import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.config.util.ConnectionPoolInfoProvider;
import ca.uhn.fhir.jpa.config.util.IConnectionPoolInfoProvider;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao;
import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao;
import ca.uhn.fhir.jpa.dao.data.ITermConceptDao;
@ -61,6 +61,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptPropertyTypeEnum;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
import ca.uhn.fhir.jpa.model.dao.JpaPid;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.sched.HapiJob;
@ -265,6 +266,8 @@ public class TermReadSvcImpl implements ITermReadSvc {
private CachingValidationSupport myCachingValidationSupport;
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Autowired
private IJpaStorageResourceParser myJpaStorageResourceParser;
@Override
public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
@ -2437,7 +2440,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
+ ForcedId.IDX_FORCEDID_TYPE_FID + " removed?");
IFhirResourceDao<CodeSystem> csDao = myDaoRegistry.getResourceDao("CodeSystem");
IBaseResource cs = csDao.toResource(resultList.get(0), false);
IBaseResource cs = myJpaStorageResourceParser.toResource(resultList.get(0), false);
return Optional.of(cs);
}
@ -2526,7 +2529,7 @@ public class TermReadSvcImpl implements ITermReadSvc {
private org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) {
Class<? extends IBaseResource> type = getFhirContext().getResourceDefinition("ValueSet").getImplementingClass();
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").toResource(type, theResourceTable, null, false);
IBaseResource valueSet = myJpaStorageResourceParser.toResource(type, theResourceTable, null, false);
return myVersionCanonicalizer.valueSetToCanonical(valueSet);
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -44,12 +44,6 @@ public class BaseCqlR4Test extends BaseJpaR4Test implements CqlProviderTestBase
@RegisterExtension
protected PartitionHelper myPartitionHelper;
// FIXME: restore?
// @Override
// public void beforeResetInterceptors() {
// myInterceptorRegistry.unregisterInterceptorsIf(t->!(t instanceof PartitionHelper.MyTestInterceptor));
// }
@Autowired
protected
DaoRegistry myDaoRegistry;

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,14 +20,15 @@ package ca.uhn.fhir.jpa.model.cross;
* #L%
*/
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Date;
public interface IBasePersistedResource extends IResourceLookup {
IIdType getIdDt();
long getVersion();
boolean isDeleted();
void setNotDeleted();
}

View File

@ -20,9 +20,10 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu2.model.Subscription;
@ -79,6 +80,30 @@ public class ModelConfig {
private boolean myDefaultSearchParamsCanBeOverridden = true;
private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
private boolean myCrossPartitionSubscription = false;
/**
* If set to true, attempt to map terminology for bulk export jobs using the
* logic in
* {@link ResponseTerminologyTranslationSvc}. Default is <code>false</code>.
*
* @since 6.3.0
*/
public boolean isNormalizeTerminologyForBulkExportJobs() {
return myNormalizeTerminologyForBulkExportJobs;
}
/**
* If set to true, attempt to map terminology for bulk export jobs using the
* logic in
* {@link ResponseTerminologyTranslationSvc}. Default is <code>false</code>.
*
* @since 6.3.0
*/
public void setNormalizeTerminologyForBulkExportJobs(boolean theNormalizeTerminologyForBulkExportJobs) {
myNormalizeTerminologyForBulkExportJobs = theNormalizeTerminologyForBulkExportJobs;
}
private boolean myNormalizeTerminologyForBulkExportJobs = false;
private String myEmailFromAddress = "noreply@unknown.com";
private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH;
/**

View File

@ -213,6 +213,16 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl
return myResourceVersion;
}
@Override
public boolean isDeleted() {
return getDeleted() != null;
}
@Override
public void setNotDeleted() {
setDeleted(null);
}
public void setVersion(long theVersion) {
myResourceVersion = theVersion;
}

View File

@ -549,6 +549,16 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
return myVersion;
}
@Override
public boolean isDeleted() {
return getDeleted() != null;
}
@Override
public void setNotDeleted() {
setDeleted(null);
}
public void setVersion(long theVersion) {
myVersion = theVersion;
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -20,11 +20,11 @@ package ca.uhn.fhir.jpa.searchparam.registry;
* #L%
*/
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.ComboSearchParamType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -179,33 +179,35 @@ public class SearchParameterCanonicalizer {
String path = theNextSp.getExpression();
RestSearchParameterTypeEnum paramType = null;
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
switch (theNextSp.getType()) {
case COMPOSITE:
paramType = RestSearchParameterTypeEnum.COMPOSITE;
break;
case DATE:
paramType = RestSearchParameterTypeEnum.DATE;
break;
case NUMBER:
paramType = RestSearchParameterTypeEnum.NUMBER;
break;
case QUANTITY:
paramType = RestSearchParameterTypeEnum.QUANTITY;
break;
case REFERENCE:
paramType = RestSearchParameterTypeEnum.REFERENCE;
break;
case STRING:
paramType = RestSearchParameterTypeEnum.STRING;
break;
case TOKEN:
paramType = RestSearchParameterTypeEnum.TOKEN;
break;
case URI:
paramType = RestSearchParameterTypeEnum.URI;
break;
case NULL:
break;
if (theNextSp.getType() != null) {
switch (theNextSp.getType()) {
case COMPOSITE:
paramType = RestSearchParameterTypeEnum.COMPOSITE;
break;
case DATE:
paramType = RestSearchParameterTypeEnum.DATE;
break;
case NUMBER:
paramType = RestSearchParameterTypeEnum.NUMBER;
break;
case QUANTITY:
paramType = RestSearchParameterTypeEnum.QUANTITY;
break;
case REFERENCE:
paramType = RestSearchParameterTypeEnum.REFERENCE;
break;
case STRING:
paramType = RestSearchParameterTypeEnum.STRING;
break;
case TOKEN:
paramType = RestSearchParameterTypeEnum.TOKEN;
break;
case URI:
paramType = RestSearchParameterTypeEnum.URI;
break;
case NULL:
break;
}
}
if (theNextSp.getStatus() != null) {
switch (theNextSp.getStatus()) {

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.HapiExtensions;
@ -94,7 +95,12 @@ public class SubscriptionValidatingInterceptor {
return;
}
CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription);
CanonicalSubscription subscription;
try {
subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription);
} catch (InternalErrorException e) {
throw new UnprocessableEntityException(Msg.code(955) + e.getMessage());
}
boolean finished = false;
if (subscription.getStatus() == null) {
throw new UnprocessableEntityException(Msg.code(8) + "Can not process submitted Subscription - Subscription.status must be populated on this server");

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,6 +22,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistryController;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader;
import ca.uhn.fhir.jpa.test.BaseJpaTest;
import ca.uhn.fhir.jpa.test.PreventDanglingInterceptorsExtension;
import ca.uhn.fhir.jpa.test.config.TestDstu2Config;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@ -60,6 +61,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
@ -213,6 +215,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Autowired
private ValidationSupportChain myJpaValidationSupportChain;
@RegisterExtension
private final PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry);
@BeforeEach
public void beforeFlushFT() {
purgeHibernateSearch(myEntityManager);
@ -232,13 +237,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
}
@Override
@AfterEach
public void afterResetInterceptors() {
super.afterResetInterceptors();
myInterceptorRegistry.unregisterAllInterceptors();
}
@Override
public FhirContext getFhirContext() {
return myFhirContext;

View File

@ -58,6 +58,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setEndpoint("http://localhost");
try {
myClient.create().resource(subs).execute();
fail();
@ -105,6 +106,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
public void testCreateWithPopulatedButInvalidStatue() {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.getChannel().setEndpoint("http://localhost");
subs.setCriteria("Observation?identifier=123");
subs.getStatusElement().setValue("aaaaa");
@ -112,7 +114,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
myClient.create().resource(subs).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server"));
assertThat(e.getMessage(), containsString("Unknown SubscriptionStatus code 'aaaaa'"));
}
}
@ -152,6 +154,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test {
Subscription subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setEndpoint("http://localhost");
subs.setStatus(SubscriptionStatusEnum.REQUESTED);
IIdType id = myClient.create().resource(subs).execute().getId();
subs.setId(id);

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -93,7 +93,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
}
@Test
public void testCreateSpWithMultiplePaths(){
public void testCreateSpWithMultiplePaths() {
SearchParameter sp = new SearchParameter();
sp.setCode("telephone-unformatted");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
@ -101,18 +101,56 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
sp.getBase().add(new CodeType("Patient"));
sp.setType(Enumerations.SearchParamType.TOKEN);
DaoMethodOutcome daoMethodOutcome = mySearchParameterDao.create(sp);
DaoMethodOutcome daoMethodOutcome;
daoMethodOutcome = mySearchParameterDao.create(sp);
assertThat(daoMethodOutcome.getId(), is(notNullValue()));
}
@Test
public void testCreateSpWithMultiplePaths2() {
SearchParameter sp = new SearchParameter();
sp.setCode("telephone-unformatted");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.setExpression("Patient.telecom.where(system='phone' or system='email')");
sp.getBase().add(new CodeType("Patient"));
sp.setType(Enumerations.SearchParamType.TOKEN);
DaoMethodOutcome daoMethodOutcome;
sp.setExpression("Patient.telecom.where(system='phone') or Patient.telecome.where(system='email')");
sp.setCode("telephone-unformatted-2");
daoMethodOutcome = mySearchParameterDao.create(sp);
assertThat(daoMethodOutcome.getId(), is(notNullValue()));
}
@Test
public void testCreateSpWithMultiplePaths3() {
SearchParameter sp = new SearchParameter();
sp.setCode("telephone-unformatted");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.setExpression("Patient.telecom.where(system='phone' or system='email')");
sp.getBase().add(new CodeType("Patient"));
sp.setType(Enumerations.SearchParamType.TOKEN);
DaoMethodOutcome daoMethodOutcome;
sp.setExpression("Patient.telecom.where(system='phone' or system='email') | Patient.telecome.where(system='email')");
sp.setCode("telephone-unformatted-3");
daoMethodOutcome = mySearchParameterDao.create(sp);
assertThat(daoMethodOutcome.getId(), is(notNullValue()));
}
@Test
public void testCreateSpWithMultiplePaths4() {
SearchParameter sp = new SearchParameter();
sp.setCode("telephone-unformatted");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.setExpression("Patient.telecom.where(system='phone' or system='email')");
sp.getBase().add(new CodeType("Patient"));
sp.setType(Enumerations.SearchParamType.TOKEN);
DaoMethodOutcome daoMethodOutcome;
sp.setExpression("Patient.telecom.where(system='phone' or system='email') | Patient.telecom.where(system='email') or Patient.telecom.where(system='mail' | system='phone')");
sp.setCode("telephone-unformatted-3");
@ -120,7 +158,6 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
assertThat(daoMethodOutcome.getId(), is(notNullValue()));
}
@Test
public void testCreateInvalidParamNoPath() {
SearchParameter fooSp = new SearchParameter();
@ -1110,21 +1147,21 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
medAdmin.setMedication(new Reference(medication));
myMedicationAdministrationDao.create(medAdmin);
runInTransaction(()->{
runInTransaction(() -> {
List<ResourceIndexedSearchParamToken> tokens = myResourceIndexedSearchParamTokenDao
.findAll()
.stream()
.filter(t -> t.getParamName().equals("medicationadministration-ingredient-medication"))
.collect(Collectors.toList());
ourLog.info("Tokens:\n * {}", tokens.stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
ourLog.info("Tokens:\n * {}", tokens.stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
assertEquals(1, tokens.size(), tokens.toString());
assertEquals(false, tokens.get(0).isMissing());
});
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add("medicationadministration-ingredient-medication", new TokenParam("system","code"));
map.add("medicationadministration-ingredient-medication", new TokenParam("system", "code"));
myCaptureQueriesListener.clear();
IBundleProvider search = myMedicationAdministrationDao.search(map);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
@ -1133,5 +1170,4 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
}
}

View File

@ -49,14 +49,16 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl("http://foo");
IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualifiedVersionless();
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualifiedVersionless();
myCodeSystemDao.delete(id);
myCodeSystemDao.delete(id, mySrd);
codeSystem = new CodeSystem();
codeSystem.setUrl("http://foo");
myCodeSystemDao.update(codeSystem, "Patient?name=FAM").getId().toUnqualifiedVersionless();
IIdType id2 = myCodeSystemDao.update(codeSystem, "CodeSystem?url=http://foo", mySrd).getId();
assertNotEquals(id.getIdPart(), id2.getIdPart());
assertEquals("1", id2.getVersionIdPart());
}
@Test

View File

@ -1117,7 +1117,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String encoded = myFhirContext.newXmlParser().encodeResourceToString(response.getOperationOutcome());
ourLog.info(encoded);
assertThat(encoded, containsString(
"<issue><severity value=\"information\"/><code value=\"informational\"/><diagnostics value=\"Successfully deleted 2 resource(s) in "));
"<diagnostics value=\"Successfully deleted 2 resource(s)"));
try {
myClient.read().resource("Patient").withId(id1).execute();
fail();
@ -1144,7 +1144,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info(response);
assertEquals(200, resp.getStatusLine().getStatusCode());
assertThat(response, containsString(
"<issue><severity value=\"warning\"/><code value=\"not-found\"/><diagnostics value=\"Unable to find resource matching URL &quot;Patient?identifier=testDeleteConditionalNoMatches&quot;. Deletion failed.\"/></issue>"));
"<diagnostics value=\"Unable to find resource matching URL &quot;Patient?identifier=testDeleteConditionalNoMatches&quot;. Nothing has been deleted.\"/>"));
} finally {
IOUtils.closeQuietly(resp);
}
@ -1214,7 +1214,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp);
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in "));
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took "));
} finally {
response.close();
}
@ -1241,7 +1241,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp);
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Deletion failed."));
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Nothing has been deleted."));
} finally {
response.close();
}
@ -1322,7 +1322,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
MethodOutcome resp = myClient.delete().resourceById(id).execute();
OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome();
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in "));
assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took"));
}
/**

View File

@ -192,14 +192,11 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
@Test
public void testPatchUsingJsonPatch_Transaction() throws Exception {
String methodName = "testPatchUsingJsonPatch_Transaction";
public void testPatchUsingJsonPatch_Transaction() {
IIdType pid1;
{
Patient patient = new Patient();
patient.setActive(true);
patient.addIdentifier().setSystem("urn:system").setValue("0");
patient.addName().setFamily(methodName).addGiven("Joe");
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
}
@ -224,6 +221,7 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
.getRequest().setUrl(pid1.getValue()).setMethod(HTTPVerb.PUT);
Bundle bundle = ourClient.transaction().withBundle(input).execute();
ourLog.info("Response: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
//Validate over all bundle response entry contents.
assertThat(bundle.getType(), is(equalTo(Bundle.BundleType.TRANSACTIONRESPONSE)));

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.3.0-PRE5-SNAPSHOT</version>
<version>6.3.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -404,18 +404,6 @@ public class Batch2CoordinatorIT extends BaseJpaR4Test {
@Test
public void testStepRunFailure_continuouslyThrows_marksJobFailed() {
// FIXME: remove
// AtomicInteger interceptorCounter = new AtomicInteger();
// myWorkChannel.addInterceptor(new ExecutorChannelInterceptor() {
// @Override
// public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) {
// if (ex != null) {
// interceptorCounter.incrementAndGet();
// ourLog.info("Work Channel Exception thrown: {}. Resending message", ex.getMessage());
// channel.send(message);
// }
// }
// });
// setup
AtomicInteger counter = new AtomicInteger();

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