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.List;
|
||||
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
|
||||
|
@ -37,12 +40,26 @@ import java.util.Optional;
|
|||
public class ParametersUtil {
|
||||
|
||||
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");
|
||||
RuntimeResourceDefinition resDef = theCtx.getResourceDefinition(theParameters.getClass());
|
||||
BaseRuntimeChildDefinition parameterChild = resDef.getChildByName("parameter");
|
||||
List<IBase> parameterReps = parameterChild.getAccessor().getValues(theParameters);
|
||||
|
||||
List<String> retVal = new ArrayList<>();
|
||||
List<T> retVal = new ArrayList<>();
|
||||
|
||||
for (IBase nextParameter : parameterReps) {
|
||||
BaseRuntimeElementCompositeDefinition<?> nextParameterDef = (BaseRuntimeElementCompositeDefinition<?>) theCtx.getElementDefinition(nextParameter.getClass());
|
||||
|
@ -62,12 +79,12 @@ public class ParametersUtil {
|
|||
valueValues
|
||||
.stream()
|
||||
.filter(t -> t instanceof IPrimitiveType<?>)
|
||||
.map(t -> ((IPrimitiveType<?>) t).getValueAsString())
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(t->((IPrimitiveType<?>) t))
|
||||
.map(theMapper)
|
||||
.filter(t -> t != null)
|
||||
.forEach(retVal::add);
|
||||
|
||||
}
|
||||
|
||||
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.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.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.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 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
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@ import ca.uhn.fhir.jpa.bulk.BulkDataExportProvider;
|
|||
import ca.uhn.fhir.jpa.bulk.BulkDataExportSvcImpl;
|
||||
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
|
||||
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.graphql.JpaStorageServices;
|
||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||
|
@ -225,6 +228,18 @@ public abstract class BaseConfig {
|
|||
return new JpaConsentContextServices();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public IPartitionConfigSvc partitionConfigSvc() {
|
||||
return new PartitionConfigSvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public PartitionManagementProvider partitionManagementProvider() {
|
||||
return new PartitionManagementProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
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.ExpungeOptions;
|
||||
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.model.cross.ResourcePersistentId;
|
||||
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.dao.data.IForcedIdDao;
|
||||
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.entity.PartitionId;
|
||||
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(ResourceHistoryTable.class));
|
||||
counter.addAndGet(expungeEverythingByType(ResourceTable.class));
|
||||
counter.addAndGet(expungeEverythingByType(PartitionEntity.class));
|
||||
myTxTemplate.execute(t -> {
|
||||
counter.addAndGet(doExpungeEverythingQuery("DELETE from " + org.hibernate.search.jpa.Search.class.getSimpleName() + " d"));
|
||||
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.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.rest.api.server.RequestDetails;
|
||||
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.servlet.ServletRequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -29,6 +28,8 @@ public class RequestPartitionHelperService {
|
|||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
@Autowired
|
||||
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
public RequestPartitionHelperService() {
|
||||
|
@ -66,7 +67,7 @@ public class RequestPartitionHelperService {
|
|||
.addIfMatchesType(ServletRequestDetails.class, theRequest);
|
||||
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_READ, params);
|
||||
|
||||
validatePartition(partitionId, theResourceType);
|
||||
validatePartition(partitionId, theResourceType, theRequest);
|
||||
}
|
||||
|
||||
return partitionId;
|
||||
|
@ -88,18 +89,29 @@ public class RequestPartitionHelperService {
|
|||
partitionId = (PartitionId) doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE, params);
|
||||
|
||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
||||
validatePartition(partitionId, resourceName);
|
||||
validatePartition(partitionId, resourceName, theRequest);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
// Make sure we're not using one of the conformance resources in a non-default partition
|
||||
if (myPartitioningBlacklist.contains(theResourceName)) {
|
||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestPartitionHelperService.class, "blacklistedResourceTypeForPartitioning", theResourceName);
|
||||
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.ISearchBuilder;
|
||||
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.SearchInclude;
|
||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||
|
|
|
@ -111,7 +111,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
SLF4JLogLevel level = SLF4JLogLevel.INFO;
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
// .logQueryBySlf4j(level, "SQL")
|
||||
.logQueryBySlf4j(level, "SQL")
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
// .countQuery(new ThreadQueryCountHolder())
|
||||
.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.jpa.api.config.DaoConfig;
|
||||
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.jpa.bulk.IBulkDataExportSvc;
|
||||
import ca.uhn.fhir.jpa.entity.TermConcept;
|
||||
|
@ -108,6 +109,8 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
protected ISearchResultCacheSvc mySearchResultCacheSvc;
|
||||
@Autowired
|
||||
protected ISearchCacheSvc mySearchCacheSvc;
|
||||
@Autowired
|
||||
protected IPartitionConfigSvc myPartitionConfigSvc;
|
||||
|
||||
@After
|
||||
public void afterPerformCleanup() {
|
||||
|
@ -115,6 +118,9 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
if (myCaptureQueriesListener != null) {
|
||||
myCaptureQueriesListener.clear();
|
||||
}
|
||||
if (myPartitionConfigSvc != null) {
|
||||
myPartitionConfigSvc.clearCaches();
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -144,6 +150,13 @@ public abstract class BaseJpaTest extends BaseTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeInitPartitions() {
|
||||
if (myPartitionConfigSvc != null) {
|
||||
myPartitionConfigSvc.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeInitMocks() {
|
||||
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.ResourceIndexedSearchParamString;
|
||||
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.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
|
||||
|
@ -100,6 +101,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
private static IValidationSupport ourJpaValidationSupportChainR4;
|
||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||
|
||||
@Autowired
|
||||
protected IPartitionConfigSvc myPartitionConfigSvc;
|
||||
@Autowired
|
||||
protected ITermReadSvc myHapiTerminologySvc;
|
||||
@Autowired
|
||||
|
@ -440,8 +443,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforePurgeDatabase() {
|
||||
@After
|
||||
public void afterPurgeDatabase() {
|
||||
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.Pointcut;
|
||||
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.partition.IPartitionConfigSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
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.TokenParam;
|
||||
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.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
@ -33,6 +36,7 @@ import org.junit.After;
|
|||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.time.LocalDate;
|
||||
|
@ -61,6 +65,8 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
private LocalDate myPartitionDate;
|
||||
private int myPartitionId;
|
||||
private boolean myHaveDroppedForcedIdUniqueConstraint;
|
||||
@Autowired
|
||||
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
|
@ -93,6 +99,10 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
myPartitionInterceptor = new MyInterceptor();
|
||||
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
|
||||
|
@ -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
|
||||
public void testCreate_ServerId_NoPartition() {
|
||||
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").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").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
|
||||
|
|
|
@ -134,6 +134,10 @@
|
|||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
|
|
|
@ -23,4 +23,18 @@ package ca.uhn.fhir.jpa.model.util;
|
|||
public class ProviderConstants {
|
||||
public static final String SUBSCRIPTION_TRIGGERING_PARAM_RESOURCE_ID = "resourceId";
|
||||
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>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</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>
|
||||
<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.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
|
@ -4,13 +4,16 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
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.StringType;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ParametersUtilR4Test {
|
||||
|
||||
|
@ -47,4 +50,16 @@ public class ParametersUtilR4Test {
|
|||
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>
|
||||
<version>${hibernate_search_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-search-engine</artifactId>
|
||||
<version>${hibernate_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
|
|
Loading…
Reference in New Issue