Work on multitenancy
This commit is contained in:
parent
8d8c657ce2
commit
e55ccf88fc
|
@ -30,6 +30,9 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for dealing with parameters resources in a version indepenedent way
|
* Utilities for dealing with parameters resources in a version indepenedent way
|
||||||
|
@ -37,12 +40,26 @@ import java.util.Optional;
|
||||||
public class ParametersUtil {
|
public class ParametersUtil {
|
||||||
|
|
||||||
public static List<String> getNamedParameterValuesAsString(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
public static List<String> getNamedParameterValuesAsString(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
||||||
|
Function<IPrimitiveType<?>, String> mapper = t -> defaultIfBlank(t.getValueAsString(), null);
|
||||||
|
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Integer> getNamedParameterValuesAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
||||||
|
Function<IPrimitiveType<?>, Integer> mapper = t -> (Integer)t.getValue();
|
||||||
|
return extractNamedParameters(theCtx, theParameters, theParameterName, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Integer> getNamedParameterValueAsInteger(FhirContext theCtx, IBaseParameters theParameters, String theParameterName) {
|
||||||
|
return getNamedParameterValuesAsInteger(theCtx, theParameters, theParameterName).stream().findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> List<T> extractNamedParameters(FhirContext theCtx, IBaseParameters theParameters, String theParameterName, Function<IPrimitiveType<?>, T> theMapper) {
|
||||||
Validate.notNull(theParameters, "theParameters must not be null");
|
Validate.notNull(theParameters, "theParameters must not be null");
|
||||||
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
|
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
|
||||||
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
|
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
|
||||||
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
|
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
|
||||||
|
|
||||||
List<String> retVal = new ArrayList<>();
|
List<T> retVal = new ArrayList<>();
|
||||||
|
|
||||||
for (IBase nextParameter : parameterReps) {
|
for (IBase nextParameter : parameterReps) {
|
||||||
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
|
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
|
||||||
|
@ -62,12 +79,12 @@ public class ParametersUtil {
|
||||||
valueValues
|
valueValues
|
||||||
.stream()
|
.stream()
|
||||||
.filter(t -> t instanceof IPrimitiveType<?>)
|
.filter(t -> t instanceof IPrimitiveType<?>)
|
||||||
.map(t -> ((IPrimitiveType<?>) t).getValueAsString())
|
.map(t->((IPrimitiveType<?>) t))
|
||||||
.filter(StringUtils::isNotBlank)
|
.map(theMapper)
|
||||||
|
.filter(t -> t != null)
|
||||||
.forEach(retVal::add);
|
.forEach(retVal::add);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,9 +138,22 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
|
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.blacklistedResourceTypeForPartitioning=Resource type {0} can not be partitioned
|
||||||
|
ca.uhn.fhir.jpa.partition.RequestPartitionHelperService.unknownPartitionId=Unknown partition ID: {0}
|
||||||
|
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
|
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
|
||||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
|
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
|
||||||
|
|
||||||
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request
|
ca.uhn.fhir.jpa.dao.index.IdHelperService.nonUniqueForcedId=Non-unique ID specified, can not process request
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.missingPartitionIdOrName=Partition must have an ID and a Name
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantCreatePartition0=Can not create a partition with ID 0 (this is a reserved value)
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.unknownPartitionId=No partition exists with ID {0}
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.invalidName=Partition name "{0}" is not valid
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantCreateDuplicatePartitionName=Partition name "{0}" is already defined
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantDeleteDefaultPartition=Can not delete default partition
|
||||||
|
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantRenameDefaultPartition=Can not rename default partition
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.partition.RequestTenantSelectingInterceptor.unknownTenantName=Unknown tenant: {0}
|
||||||
|
|
||||||
|
|
|
@ -146,6 +146,13 @@ Some security audit tools require that servers return an HTTP 405 if an unsuppor
|
||||||
* [BanUnsupportedHttpMethodsInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.html)
|
* [BanUnsupportedHttpMethodsInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.html)
|
||||||
* [BanUnsupportedHttpMethodsInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.java)
|
* [BanUnsupportedHttpMethodsInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BanUnsupportedHttpMethodsInterceptor.java)
|
||||||
|
|
||||||
|
# Subscription: Subscription Debug Log Interceptor
|
||||||
|
|
||||||
|
When using Subscriptions, the debug log interceptor can be used to add a number of additional lines to the server logs showing the internals of the subscription processing pipeline.
|
||||||
|
|
||||||
|
* [SubscriptionDebugLogInterceptor JavaDoc](/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.html)
|
||||||
|
* [SubscriptionDebugLogInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master//hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/subscription/util/SubscriptionDebugLogInterceptor.java)
|
||||||
|
|
||||||
|
|
||||||
# Request Pre-Processing: Override Meta.source
|
# Request Pre-Processing: Override Meta.source
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,10 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
|
||||||
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
|
||||||
|
import ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl;
|
||||||
|
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
||||||
|
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
||||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||||
|
@ -225,6 +228,18 @@ public abstract class BaseConfig {
|
||||||
return new JpaConsentContextServices();
|
return new JpaConsentContextServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Lazy
|
||||||
|
public IPartitionConfigSvc partitionConfigSvc() {
|
||||||
|
return new PartitionConfigSvcImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Lazy
|
||||||
|
public PartitionManagementProvider partitionManagementProvider() {
|
||||||
|
return new PartitionManagementProvider();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Lazy
|
@Lazy
|
||||||
public TerminologyUploaderProvider terminologyUploaderProvider() {
|
public TerminologyUploaderProvider terminologyUploaderProvider() {
|
||||||
|
|
|
@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
|
||||||
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
|
||||||
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
|
||||||
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
|
||||||
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
|
||||||
|
|
|
@ -23,7 +23,7 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
|
||||||
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.data;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import org.checkerframework.checker.nullness.Opt;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2020 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface IPartitionDao extends JpaRepository<PartitionEntity, Integer> {
|
||||||
|
|
||||||
|
@Query("SELECT p FROM PartitionEntity p WHERE p.myName = :name")
|
||||||
|
Optional<PartitionEntity> findForName(@Param("name") String theName);
|
||||||
|
|
||||||
|
}
|
|
@ -121,6 +121,7 @@ public class ExpungeEverythingService {
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class));
|
counter.addAndGet(expungeEverythingByType(ResourceHistoryProvenanceEntity.class));
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
|
counter.addAndGet(expungeEverythingByType(ResourceHistoryTable.class));
|
||||||
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
|
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
|
||||||
|
counter.addAndGet(expungeEverythingByType(PartitionEntity.class));
|
||||||
myTxTemplate.execute(t -> {
|
myTxTemplate.execute(t -> {
|
||||||
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d"));
|
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d"));
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ca.uhn.fhir.jpa.entity;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "HFJ_PARTITION", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(name = "IDX_PART_NAME", columnNames = {"PART_NAME"})
|
||||||
|
})
|
||||||
|
public class PartitionEntity {
|
||||||
|
|
||||||
|
public static final int MAX_NAME_LENGTH = 200;
|
||||||
|
public static final int MAX_DESC_LENGTH = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that unlike most PID columns in HAPI FHIR JPA, this one is an Integer, and isn't
|
||||||
|
* auto assigned.
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@Column(name = "PART_ID", nullable = false)
|
||||||
|
private Integer myId;
|
||||||
|
@Column(name = "PART_NAME", length = MAX_NAME_LENGTH, nullable = false)
|
||||||
|
private String myName;
|
||||||
|
@Column(name = "PART_DESC", length = MAX_DESC_LENGTH, nullable = true)
|
||||||
|
private String myDescription;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return myId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartitionEntity setId(Integer theId) {
|
||||||
|
myId = theId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return myName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartitionEntity setName(String theName) {
|
||||||
|
myName = theName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return myDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String theDescription) {
|
||||||
|
myDescription = theDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
|
||||||
|
public interface IPartitionConfigSvc {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is mostly here for unit test purposes. Regular code is not expected to call this method directly.
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalArgumentException If the name is not known
|
||||||
|
*/
|
||||||
|
PartitionEntity getPartitionByName(String theName) throws IllegalArgumentException;
|
||||||
|
|
||||||
|
PartitionEntity getPartitionById(Integer theId);
|
||||||
|
|
||||||
|
void clearCaches();
|
||||||
|
|
||||||
|
PartitionEntity createPartition(PartitionEntity thePartition);
|
||||||
|
|
||||||
|
PartitionEntity updatePartition(PartitionEntity thePartition);
|
||||||
|
|
||||||
|
void deletePartition(Integer thePartitionId);
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IPartitionDao;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
public class PartitionConfigSvcImpl implements IPartitionConfigSvc {
|
||||||
|
|
||||||
|
public static final int DEFAULT_PERSISTED_PARTITION_ID = 0;
|
||||||
|
private static final String DEFAULT_PERSISTED_PARTITION_NAME = "DEFAULT";
|
||||||
|
private static final String DEFAULT_PERSISTED_PARTITION_DESC = "Default partition";
|
||||||
|
private static final Pattern PARTITION_NAME_VALID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+");
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(PartitionConfigSvcImpl.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PlatformTransactionManager myTxManager;
|
||||||
|
@Autowired
|
||||||
|
private IPartitionDao myPartitionDao;
|
||||||
|
|
||||||
|
private LoadingCache<String, PartitionEntity> myNameToPartitionCache;
|
||||||
|
private LoadingCache<Integer, PartitionEntity> myIdToPartitionCache;
|
||||||
|
private TransactionTemplate myTxTemplate;
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myFhirCtx;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostConstruct
|
||||||
|
public void start() {
|
||||||
|
myNameToPartitionCache = Caffeine
|
||||||
|
.newBuilder()
|
||||||
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
|
.build(new NameToPartitionCacheLoader());
|
||||||
|
myIdToPartitionCache = Caffeine
|
||||||
|
.newBuilder()
|
||||||
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
|
.build(new IdToPartitionCacheLoader());
|
||||||
|
myTxTemplate = new TransactionTemplate(myTxManager);
|
||||||
|
|
||||||
|
// Create default partition definition if it doesn't already exist
|
||||||
|
myTxTemplate.executeWithoutResult(t -> {
|
||||||
|
if (myPartitionDao.findById(DEFAULT_PERSISTED_PARTITION_ID).isPresent() == false) {
|
||||||
|
ourLog.info("Creating default partition definition");
|
||||||
|
PartitionEntity partitionEntity = new PartitionEntity();
|
||||||
|
partitionEntity.setId(DEFAULT_PERSISTED_PARTITION_ID);
|
||||||
|
partitionEntity.setName(DEFAULT_PERSISTED_PARTITION_NAME);
|
||||||
|
partitionEntity.setDescription(DEFAULT_PERSISTED_PARTITION_DESC);
|
||||||
|
myPartitionDao.save(partitionEntity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartitionEntity getPartitionByName(String theName) {
|
||||||
|
Validate.notBlank(theName, "The name must not be null or blank");
|
||||||
|
return myNameToPartitionCache.get(theName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartitionEntity getPartitionById(Integer theId) {
|
||||||
|
Validate.notNull(theId, "The ID must not be null");
|
||||||
|
return myIdToPartitionCache.get(theId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCaches() {
|
||||||
|
myNameToPartitionCache.invalidateAll();
|
||||||
|
myIdToPartitionCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public PartitionEntity createPartition(PartitionEntity thePartition) {
|
||||||
|
validateHaveValidPartitionIdAndName(thePartition);
|
||||||
|
validatePartitionNameDoesntAlreadyExist(thePartition.getName());
|
||||||
|
|
||||||
|
if (thePartition.getId() == DEFAULT_PERSISTED_PARTITION_ID) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessage(PartitionConfigSvcImpl.class, "cantCreatePartition0");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ourLog.info("Creating new partition with ID {} and Name {}", thePartition.getId(), thePartition.getName());
|
||||||
|
|
||||||
|
myPartitionDao.save(thePartition);
|
||||||
|
return thePartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public PartitionEntity updatePartition(PartitionEntity thePartition) {
|
||||||
|
validateHaveValidPartitionIdAndName(thePartition);
|
||||||
|
|
||||||
|
Optional<PartitionEntity> existingPartitionOpt = myPartitionDao.findById(thePartition.getId());
|
||||||
|
if (existingPartitionOpt.isPresent() == false) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "unknownPartitionId", thePartition.getId());
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionEntity existingPartition = existingPartitionOpt.get();
|
||||||
|
if (!thePartition.getName().equalsIgnoreCase(existingPartition.getName())) {
|
||||||
|
validatePartitionNameDoesntAlreadyExist(thePartition.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEFAULT_PERSISTED_PARTITION_ID == thePartition.getId()) {
|
||||||
|
if (!DEFAULT_PERSISTED_PARTITION_NAME.equals(thePartition.getName())) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "cantRenameDefaultPartition");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPartition.setName(thePartition.getName());
|
||||||
|
existingPartition.setDescription(thePartition.getDescription());
|
||||||
|
myPartitionDao.save(existingPartition);
|
||||||
|
clearCaches();
|
||||||
|
return existingPartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void deletePartition(Integer thePartitionId) {
|
||||||
|
Validate.notNull(thePartitionId);
|
||||||
|
|
||||||
|
if (DEFAULT_PERSISTED_PARTITION_ID == thePartitionId) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "cantDeleteDefaultPartition");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<PartitionEntity> partition = myPartitionDao.findById(thePartitionId);
|
||||||
|
if (!partition.isPresent()) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "unknownPartitionId", thePartitionId);
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
myPartitionDao.delete(partition.get());
|
||||||
|
|
||||||
|
clearCaches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validatePartitionNameDoesntAlreadyExist(String theName) {
|
||||||
|
if (myPartitionDao.findForName(theName).isPresent()) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "cantCreateDuplicatePartitionName", theName);
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateHaveValidPartitionIdAndName(PartitionEntity thePartition) {
|
||||||
|
if (thePartition.getId() == null || isBlank(thePartition.getName())) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessage(PartitionConfigSvcImpl.class, "missingPartitionIdOrName");
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PARTITION_NAME_VALID_PATTERN.matcher(thePartition.getName()).matches()) {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "invalidName", thePartition.getName());
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NameToPartitionCacheLoader implements @NonNull CacheLoader<String, PartitionEntity> {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public PartitionEntity load(@NonNull String theName) {
|
||||||
|
return myTxTemplate.execute(t -> myPartitionDao
|
||||||
|
.findForName(theName)
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "invalidName", theName);
|
||||||
|
return new IllegalArgumentException(msg);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class IdToPartitionCacheLoader implements @NonNull CacheLoader<Integer, PartitionEntity> {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public PartitionEntity load(@NonNull Integer theId) {
|
||||||
|
return myTxTemplate.execute(t -> myPartitionDao
|
||||||
|
.findById(theId)
|
||||||
|
.orElseThrow(() -> {
|
||||||
|
String msg = myFhirCtx.getLocalizer().getMessageSanitized(PartitionConfigSvcImpl.class, "unknownPartitionId", theId);
|
||||||
|
return new IllegalArgumentException(msg);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
import ca.uhn.fhir.util.ParametersUtil;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
public class PartitionManagementProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myCtx;
|
||||||
|
@Autowired
|
||||||
|
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Partition:
|
||||||
|
* <code>
|
||||||
|
* $partition-management-add-partition
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
||||||
|
public IBaseParameters addPartition(
|
||||||
|
@ResourceParam IBaseParameters theRequest,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
||||||
|
) {
|
||||||
|
|
||||||
|
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
||||||
|
PartitionEntity output = myPartitionConfigSvc.createPartition(input);
|
||||||
|
IBaseParameters retVal = prepareOutput(output);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Partition:
|
||||||
|
* <code>
|
||||||
|
* $partition-management-update-partition
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
||||||
|
public IBaseParameters updatePartition(
|
||||||
|
@ResourceParam IBaseParameters theRequest,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription
|
||||||
|
) {
|
||||||
|
|
||||||
|
PartitionEntity input = parseInput(thePartitionId, thePartitionName, thePartitionDescription);
|
||||||
|
PartitionEntity output = myPartitionConfigSvc.updatePartition(input);
|
||||||
|
IBaseParameters retVal = prepareOutput(output);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Partition:
|
||||||
|
* <code>
|
||||||
|
* $partition-management-delete-partition
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
@Operation(name = ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
||||||
|
public IBaseParameters updatePartition(
|
||||||
|
@ResourceParam IBaseParameters theRequest,
|
||||||
|
@OperationParam(name=ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId
|
||||||
|
) {
|
||||||
|
|
||||||
|
myPartitionConfigSvc.deletePartition(thePartitionId.getValue());
|
||||||
|
|
||||||
|
IBaseParameters retVal = ParametersUtil.newInstance(myCtx);
|
||||||
|
ParametersUtil.addParameterToParametersString(myCtx, retVal, "message", "Success");
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBaseParameters prepareOutput(PartitionEntity theOutput) {
|
||||||
|
IBaseParameters retVal = ParametersUtil.newInstance(myCtx);
|
||||||
|
ParametersUtil.addParameterToParametersInteger(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, theOutput.getId());
|
||||||
|
ParametersUtil.addParameterToParametersCode(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, theOutput.getName());
|
||||||
|
if (isNotBlank(theOutput.getDescription())) {
|
||||||
|
ParametersUtil.addParameterToParametersString(myCtx, retVal, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, theOutput.getDescription());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PartitionEntity parseInput(@OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, min = 1, max = 1, typeName = "integer") IPrimitiveType<Integer> thePartitionId, @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, min = 1, max = 1, typeName = "code") IPrimitiveType<String> thePartitionName, @OperationParam(name = ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, min = 0, max = 1, typeName = "string") IPrimitiveType<String> thePartitionDescription) {
|
||||||
|
PartitionEntity input = new PartitionEntity();
|
||||||
|
if (thePartitionId != null) {
|
||||||
|
input.setId(thePartitionId.getValue());
|
||||||
|
}
|
||||||
|
if (thePartitionName != null) {
|
||||||
|
input.setName(thePartitionName.getValue());
|
||||||
|
}
|
||||||
|
if (thePartitionDescription != null) {
|
||||||
|
input.setDescription(thePartitionDescription.getValue());
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.dao.partition;
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
|
@ -8,7 +8,6 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -29,6 +28,8 @@ public class RequestPartitionHelperService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
@Autowired
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
|
|
||||||
public RequestPartitionHelperService() {
|
public RequestPartitionHelperService() {
|
||||||
|
@ -66,7 +67,7 @@ public class RequestPartitionHelperService {
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
||||||
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
|
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
|
||||||
|
|
||||||
validatePartition(partitionId, theResourceType);
|
validatePartition(partitionId, theResourceType, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return partitionId;
|
return partitionId;
|
||||||
|
@ -88,18 +89,29 @@ public class RequestPartitionHelperService {
|
||||||
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
|
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
|
||||||
|
|
||||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
||||||
validatePartition(partitionId, resourceName);
|
validatePartition(partitionId, resourceName, theRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
return partitionId;
|
return partitionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName) {
|
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName, RequestDetails theRequestDetails) {
|
||||||
if (thePartitionId != null && thePartitionId.getPartitionId() != null) {
|
if (thePartitionId != null && thePartitionId.getPartitionId() != null) {
|
||||||
|
|
||||||
|
// Make sure we're not using one of the conformance resources in a non-default partition
|
||||||
if (myPartitioningBlacklist.contains(theResourceName)) {
|
if (myPartitioningBlacklist.contains(theResourceName)) {
|
||||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
|
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
|
||||||
throw new UnprocessableEntityException(msg);
|
throw new UnprocessableEntityException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the partition exists
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.getPartitionById(thePartitionId.getPartitionId());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "unknownPartitionId", thePartitionId.getPartitionId());
|
||||||
|
throw new InvalidRequestException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.jpa.model.entity.PartitionId;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
public class RequestTenantSelectingInterceptor {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myFhirContext;
|
||||||
|
|
||||||
|
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
|
||||||
|
public PartitionId PartitionIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
|
||||||
|
return extractPartitionIdFromRequest(theRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
|
||||||
|
public PartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) {
|
||||||
|
return extractPartitionIdFromRequest(theRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private PartitionId extractPartitionIdFromRequest(ServletRequestDetails theRequestDetails) {
|
||||||
|
String tenantId = theRequestDetails.getTenantId();
|
||||||
|
|
||||||
|
PartitionEntity partition;
|
||||||
|
try {
|
||||||
|
partition = myPartitionConfigSvc.getPartitionByName(tenantId);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestTenantSelectingInterceptor.class, "unknownTenantName", tenantId);
|
||||||
|
throw new ResourceNotFoundException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionId retVal = new PartitionId();
|
||||||
|
retVal.setPartitionId(partition.getId());
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
import ca.uhn.fhir.jpa.dao.IResultIterator;
|
||||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
import ca.uhn.fhir.jpa.dao.SearchBuilderFactory;
|
||||||
import ca.uhn.fhir.jpa.dao.partition.RequestPartitionHelperService;
|
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperService;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
import ca.uhn.fhir.jpa.entity.SearchInclude;
|
||||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
SLF4JLogLevel level = SLF4JLogLevel.INFO;
|
SLF4JLogLevel level = SLF4JLogLevel.INFO;
|
||||||
DataSource dataSource = ProxyDataSourceBuilder
|
DataSource dataSource = ProxyDataSourceBuilder
|
||||||
.create(retVal)
|
.create(retVal)
|
||||||
// .logQueryBySlf4j(level, "SQL")
|
.logQueryBySlf4j(level, "SQL")
|
||||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||||
// .countQuery(new ThreadQueryCountHolder())
|
// .countQuery(new ThreadQueryCountHolder())
|
||||||
.beforeQuery(new BlockLargeNumbersOfParamsListener())
|
.beforeQuery(new BlockLargeNumbersOfParamsListener())
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
|
||||||
import ca.uhn.fhir.test.BaseTest;
|
import ca.uhn.fhir.test.BaseTest;
|
||||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||||
|
@ -108,6 +109,8 @@ public abstract class BaseJpaTest extends BaseTest {
|
||||||
protected ISearchResultCacheSvc mySearchResultCacheSvc;
|
protected ISearchResultCacheSvc mySearchResultCacheSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ISearchCacheSvc mySearchCacheSvc;
|
protected ISearchCacheSvc mySearchCacheSvc;
|
||||||
|
@Autowired
|
||||||
|
protected IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterPerformCleanup() {
|
public void afterPerformCleanup() {
|
||||||
|
@ -115,6 +118,9 @@ public abstract class BaseJpaTest extends BaseTest {
|
||||||
if (myCaptureQueriesListener != null) {
|
if (myCaptureQueriesListener != null) {
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
}
|
}
|
||||||
|
if (myPartitionConfigSvc != null) {
|
||||||
|
myPartitionConfigSvc.clearCaches();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -144,6 +150,13 @@ public abstract class BaseJpaTest extends BaseTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeInitPartitions() {
|
||||||
|
if (myPartitionConfigSvc != null) {
|
||||||
|
myPartitionConfigSvc.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void beforeInitMocks() {
|
public void beforeInitMocks() {
|
||||||
myRequestOperationCallback = new InterceptorService();
|
myRequestOperationCallback = new InterceptorService();
|
||||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
|
||||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||||
|
@ -100,6 +101,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
||||||
private static IValidationSupport ourJpaValidationSupportChainR4;
|
private static IValidationSupport ourJpaValidationSupportChainR4;
|
||||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ITermReadSvc myHapiTerminologySvc;
|
protected ITermReadSvc myHapiTerminologySvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -440,8 +443,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
||||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
|
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@After
|
||||||
public void beforePurgeDatabase() {
|
public void afterPurgeDatabase() {
|
||||||
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry, myBulkDataExportSvc);
|
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry, myBulkDataExportSvc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
import ca.uhn.fhir.jpa.model.entity.*;
|
import ca.uhn.fhir.jpa.model.entity.*;
|
||||||
|
import ca.uhn.fhir.jpa.partition.IPartitionConfigSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
@ -13,6 +15,7 @@ import ca.uhn.fhir.rest.param.DateParam;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
@ -33,6 +36,7 @@ import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
@ -61,6 +65,8 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
||||||
private LocalDate myPartitionDate;
|
private LocalDate myPartitionDate;
|
||||||
private int myPartitionId;
|
private int myPartitionId;
|
||||||
private boolean myHaveDroppedForcedIdUniqueConstraint;
|
private boolean myHaveDroppedForcedIdUniqueConstraint;
|
||||||
|
@Autowired
|
||||||
|
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void after() {
|
public void after() {
|
||||||
|
@ -93,6 +99,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
||||||
|
|
||||||
myPartitionInterceptor = new MyInterceptor();
|
myPartitionInterceptor = new MyInterceptor();
|
||||||
myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
|
myInterceptorRegistry.registerInterceptor(myPartitionInterceptor);
|
||||||
|
|
||||||
|
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(1).setName("PART-1"));
|
||||||
|
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(2).setName("PART-2"));
|
||||||
|
myPartitionConfigSvc.createPartition(new PartitionEntity().setId(3).setName("PART-3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -155,6 +165,22 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate_UnknownPartition() {
|
||||||
|
addCreatePartition(99, null);
|
||||||
|
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("system").setValue("value");
|
||||||
|
p.setBirthDate(new Date());
|
||||||
|
try {
|
||||||
|
myPatientDao.create(p);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Unknown partition ID: 99", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreate_ServerId_NoPartition() {
|
public void testCreate_ServerId_NoPartition() {
|
||||||
addCreateNoPartition();
|
addCreateNoPartition();
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class PartitionConfigSvcImplTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateAndFetchPartition() {
|
||||||
|
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
|
||||||
|
partition = myPartitionConfigSvc.getPartitionById(123);
|
||||||
|
assertEquals("NAME123", partition.getName());
|
||||||
|
|
||||||
|
partition = myPartitionConfigSvc.getPartitionByName("NAME123");
|
||||||
|
assertEquals("NAME123", partition.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletePartition() {
|
||||||
|
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
|
||||||
|
partition = myPartitionConfigSvc.getPartitionById(123);
|
||||||
|
assertEquals("NAME123", partition.getName());
|
||||||
|
|
||||||
|
myPartitionConfigSvc.deletePartition(123);
|
||||||
|
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.getPartitionById(123);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertEquals("No partition exists with ID 123", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeletePartition_TryToDeleteDefault() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.deletePartition(0);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Can not delete default partition", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition_TryToUseExistingName() {
|
||||||
|
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
|
||||||
|
partition = new PartitionEntity();
|
||||||
|
partition.setId(111);
|
||||||
|
partition.setName("NAME111");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
|
||||||
|
partition = new PartitionEntity();
|
||||||
|
partition.setId(111);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.updatePartition(partition);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Partition name \"NAME123\" is already defined", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition_TryToRenameDefault() {
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(0);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.updatePartition(partition);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Can not rename default partition", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition() {
|
||||||
|
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
|
||||||
|
partition = myPartitionConfigSvc.getPartitionById(123);
|
||||||
|
assertEquals("NAME123", partition.getName());
|
||||||
|
|
||||||
|
partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME-NEW");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
myPartitionConfigSvc.updatePartition(partition);
|
||||||
|
|
||||||
|
partition = myPartitionConfigSvc.getPartitionById(123);
|
||||||
|
assertEquals("NAME-NEW", partition.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePartition_InvalidName() {
|
||||||
|
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME 123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Partition name \"NAME 123\" is not valid", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreatePartition_0Blocked() {
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(0);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.createPartition(partition);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Can not create a partition with ID 0 (this is a reserved value)", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdatePartition_UnknownPartitionBlocked() {
|
||||||
|
PartitionEntity partition = new PartitionEntity();
|
||||||
|
partition.setId(123);
|
||||||
|
partition.setName("NAME123");
|
||||||
|
partition.setDescription("A description");
|
||||||
|
try {
|
||||||
|
myPartitionConfigSvc.updatePartition(partition);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("No partition exists with ID 123", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ca.uhn.fhir.jpa.partition;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||||
|
import ca.uhn.fhir.jpa.model.util.ProviderConstants;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
|
import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
|
||||||
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = PartitionManagementProviderTest.MyConfig.class)
|
||||||
|
public class PartitionManagementProviderTest {
|
||||||
|
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(PartitionManagementProviderTest.class);
|
||||||
|
private static FhirContext ourCtx = FhirContext.forR4();
|
||||||
|
@ClassRule
|
||||||
|
public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
|
||||||
|
@MockBean
|
||||||
|
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||||
|
@Autowired
|
||||||
|
private PartitionManagementProvider myPartitionManagementProvider;
|
||||||
|
private IGenericClient myClient;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourServerRule.getRestfulServer().registerProvider(myPartitionManagementProvider);
|
||||||
|
myClient = ourServerRule.getFhirClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
ourServerRule.getRestfulServer().unregisterProvider(myPartitionManagementProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddPartition() {
|
||||||
|
when(myPartitionConfigSvc.createPartition(any())).thenAnswer(t -> t.getArgument(0, PartitionEntity.class));
|
||||||
|
|
||||||
|
Parameters input = new Parameters();
|
||||||
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(123));
|
||||||
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("PARTITION-123"));
|
||||||
|
input.addParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC, new CodeType("a description"));
|
||||||
|
|
||||||
|
Parameters response = myClient
|
||||||
|
.operation()
|
||||||
|
.onServer()
|
||||||
|
.named(ProviderConstants.PARTITION_MANAGEMENT_ADD_PARTITION)
|
||||||
|
.withParameters(input)
|
||||||
|
.encodedXml()
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
ourLog.info("Response:\n{}", ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(response));
|
||||||
|
verify(myPartitionConfigSvc, times(1)).createPartition(any());
|
||||||
|
verifyNoMoreInteractions(myPartitionConfigSvc);
|
||||||
|
|
||||||
|
assertEquals(123, ((IntegerType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID)).getValue().intValue());
|
||||||
|
assertEquals("PARTITION-123", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME)).getValue());
|
||||||
|
assertEquals("a description", ((StringType) response.getParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_DESC)).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class MyConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PartitionManagementProvider partitionManagementProvider() {
|
||||||
|
return new PartitionManagementProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public FhirContext fhirContext() {
|
||||||
|
return ourCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,9 +69,14 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
version.onTable("HFJ_RES_VER").addForeignKey("20200218.3", "FK_RESOURCE_HISTORY_RESOURCE").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
|
version.onTable("HFJ_RES_VER").addForeignKey("20200218.3", "FK_RESOURCE_HISTORY_RESOURCE").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID");
|
||||||
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
|
version.onTable("HFJ_RES_VER").modifyColumn("20200220.1", "RES_ID").nonNullable().failureAllowed().withType(BaseTableColumnTypeTask.ColumnTypeEnum.LONG);
|
||||||
|
|
||||||
// Add mlutiitenancy
|
// Add multiitenancy
|
||||||
version.onTable("HFJ_RESOURCE").dropIndex("20200327.1", "IDX_RES_PROFILE");
|
version.onTable("HFJ_RESOURCE").dropIndex("20200327.1", "IDX_RES_PROFILE");
|
||||||
version.onTable("HFJ_RESOURCE").dropColumn("20200327.2", "RES_PROFILE");
|
version.onTable("HFJ_RESOURCE").dropColumn("20200327.2", "RES_PROFILE");
|
||||||
|
|
||||||
|
Builder.BuilderAddTableByColumns partition = version.addTableByColumns("20200410.1", "HFJ_PARTITION", "PART_ID");
|
||||||
|
partition.addColumn("PART_ID").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.INT);
|
||||||
|
partition.addColumn("PART_NAME").nonNullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
|
||||||
|
partition.addColumn("PART_DESC").nullable().type(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init420() { // 20191015 - 20200217
|
protected void init420() { // 20191015 - 20200217
|
||||||
|
|
|
@ -134,6 +134,10 @@
|
||||||
<artifactId>spring-test</artifactId>
|
<artifactId>spring-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.validator</groupId>
|
||||||
|
<artifactId>hibernate-validator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -23,4 +23,18 @@ package ca.uhn.fhir.jpa.model.util;
|
||||||
public class ProviderConstants {
|
public class ProviderConstants {
|
||||||
public static final String SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID = "resourceId";
|
public static final String SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID = "resourceId";
|
||||||
public static final String SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL = "searchUrl";
|
public static final String SUBSCRIPTION_TRIGGERING_PARAM_SEARCH_URL = "searchUrl";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operation name: add partition
|
||||||
|
*/
|
||||||
|
public static final String PARTITION_MANAGEMENT_ADD_PARTITION = "partition-management-add-partition";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operation name: update partition
|
||||||
|
*/
|
||||||
|
public static final String PARTITION_MANAGEMENT_MODIFY_PARTITION = "partition-management-update-partition";
|
||||||
|
|
||||||
|
public static final String PARTITION_MANAGEMENT_PARTITION_ID = "partitionId";
|
||||||
|
public static final String PARTITION_MANAGEMENT_PARTITION_NAME = "partitionName";
|
||||||
|
public static final String PARTITION_MANAGEMENT_PARTITION_DESC = "description";
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,22 @@
|
||||||
<groupId>org.springframework.retry</groupId>
|
<groupId>org.springframework.retry</groupId>
|
||||||
<artifactId>spring-retry</artifactId>
|
<artifactId>spring-retry</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-search-engine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jscience</groupId>
|
||||||
|
<artifactId>jscience</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.quartz-scheduler</groupId>
|
<groupId>org.quartz-scheduler</groupId>
|
||||||
|
|
|
@ -44,7 +44,6 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
|
@ -4,13 +4,16 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import org.hamcrest.MatcherAssert;
|
import org.hamcrest.MatcherAssert;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||||
|
import org.hl7.fhir.r4.model.IntegerType;
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class ParametersUtilR4Test {
|
public class ParametersUtilR4Test {
|
||||||
|
|
||||||
|
@ -47,4 +50,16 @@ public class ParametersUtilR4Test {
|
||||||
MatcherAssert.assertThat(values, Matchers.contains("VALUE1", "VALUE2"));
|
MatcherAssert.assertThat(values, Matchers.contains("VALUE1", "VALUE2"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetValueAsInteger(){
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter()
|
||||||
|
.setName("foo")
|
||||||
|
.setValue(new IntegerType(123));
|
||||||
|
|
||||||
|
Optional<Integer> value = ParametersUtil.getNamedParameterValueAsInteger(FhirContext.forR4(), p, "foo");
|
||||||
|
assertTrue(value.isPresent());
|
||||||
|
assertEquals(123, value.get().intValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
5
pom.xml
5
pom.xml
|
@ -1318,6 +1318,11 @@
|
||||||
<artifactId>hibernate-search-elasticsearch</artifactId>
|
<artifactId>hibernate-search-elasticsearch</artifactId>
|
||||||
<version>${hibernate_search_version}</version>
|
<version>${hibernate_search_version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate</groupId>
|
||||||
|
<artifactId>hibernate-search-engine</artifactId>
|
||||||
|
<version>${hibernate_version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.javassist</groupId>
|
<groupId>org.javassist</groupId>
|
||||||
<artifactId>javassist</artifactId>
|
<artifactId>javassist</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue