Tenancy docs
This commit is contained in:
parent
6c760c9bff
commit
cd3e1c5e72
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.interceptor.model;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
|
@ -27,17 +28,23 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|||
|
||||
public class PartitionId {
|
||||
|
||||
private Integer myPartitionId;
|
||||
private LocalDate myPartitionDate;
|
||||
private final Integer myPartitionId;
|
||||
private final LocalDate myPartitionDate;
|
||||
private final String myPartitionName;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public PartitionId(@Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
||||
private PartitionId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
||||
myPartitionName = thePartitionName;
|
||||
myPartitionId = thePartitionId;
|
||||
myPartitionDate = thePartitionDate;
|
||||
}
|
||||
|
||||
public String getPartitionName() {
|
||||
return myPartitionName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getPartitionId() {
|
||||
return myPartitionId;
|
||||
|
@ -71,4 +78,29 @@ public class PartitionId {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PartitionId forPartitionId(@Nullable Integer thePartitionId) {
|
||||
return forPartitionId(thePartitionId, null);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PartitionId forPartitionId(@Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
||||
return new PartitionId(null, thePartitionId, thePartitionDate);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PartitionId forPartitionName(@Nullable String thePartitionName) {
|
||||
return forPartitionName(thePartitionName, null);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PartitionId forPartitionName(@Nullable String thePartitionName, @Nullable LocalDate thePartitionDate) {
|
||||
return new PartitionId(thePartitionName, null, thePartitionDate);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static PartitionId forPartitionNameAndId(@Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) {
|
||||
return new PartitionId(thePartitionName, thePartitionId, thePartitionDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply
|
|||
|
||||
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.partition.RequestPartitionHelperService.unknownPartitionName=Unknown partition name: {0}
|
||||
|
||||
|
||||
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
|
||||
|
@ -157,4 +158,3 @@ ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantDeleteDefaultPartition=Can
|
|||
ca.uhn.fhir.jpa.partition.PartitionConfigSvcImpl.cantRenameDefaultPartition=Can not rename default partition
|
||||
|
||||
ca.uhn.fhir.jpa.partition.RequestTenantPartitionInterceptor.unknownTenantName=Unknown tenant: {0}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ This interceptor can implement the hooks shown below.
|
|||
|
||||
## Identify Partition for Create (Required)
|
||||
|
||||
A hook against the [Pointcut#STORAGE_PARTITION_IDENTIFY_CREATE](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is being created in order to determine the partition to create the resource in.
|
||||
A hook against the [`Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE`](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is being created in order to determine the partition to create the resource in.
|
||||
|
||||
The criteria for determining the partition will depend on your use case. For example:
|
||||
|
||||
|
@ -56,7 +56,9 @@ The criteria for determining the partition will depend on your use case. For exa
|
|||
|
||||
## Identify Partition for Read (Optional)
|
||||
|
||||
A hook against the [Pointcut#STORAGE_PARTITION_IDENTIFY_CREATE](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is being created in order to determine the partition to create the resource in.
|
||||
A hook against the [`Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE`](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html#STORAGE_PARTITION_IDENTIFY_CREATE) pointcut must be registered, and this hook method will be invoked every time a resource is being created in order to determine the partition to create the resource in.
|
||||
|
||||
## Example: Using Request Tenants
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -26,11 +26,14 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.interceptor.model.PartitionId;
|
||||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionConfig;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
@ -94,7 +97,7 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||
validatePartition(partitionId, theResourceType);
|
||||
}
|
||||
|
||||
return partitionId;
|
||||
return normalize(partitionId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +120,51 @@ public class RequestPartitionHelperService implements IRequestPartitionHelperSer
|
|||
validatePartition(partitionId, resourceName);
|
||||
}
|
||||
|
||||
return partitionId;
|
||||
return normalize(partitionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the partition only has a name but not an ID, this method resolves the ID
|
||||
* @param thePartitionId
|
||||
* @return
|
||||
*/
|
||||
private PartitionId normalize(PartitionId thePartitionId) {
|
||||
if (thePartitionId != null) {
|
||||
if (thePartitionId.getPartitionName() != null) {
|
||||
|
||||
PartitionEntity partition;
|
||||
try {
|
||||
partition = myPartitionConfigSvc.getPartitionByName(thePartitionId.getPartitionName());
|
||||
} catch (IllegalArgumentException e) {
|
||||
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionName", thePartitionId.getPartitionName());
|
||||
throw new ResourceNotFoundException(msg);
|
||||
}
|
||||
|
||||
if (thePartitionId.getPartitionId() != null) {
|
||||
Validate.isTrue(thePartitionId.getPartitionId().equals(partition.getId()), "Partition name %s does not match ID %n", thePartitionId.getPartitionName(), thePartitionId.getPartitionId());
|
||||
return thePartitionId;
|
||||
} else {
|
||||
return PartitionId.forPartitionNameAndId(thePartitionId.getPartitionName(), partition.getId(), thePartitionId.getPartitionDate());
|
||||
}
|
||||
}
|
||||
|
||||
if (thePartitionId.getPartitionId() != null) {
|
||||
PartitionEntity partition;
|
||||
try {
|
||||
partition = myPartitionConfigSvc.getPartitionById(thePartitionId.getPartitionId());
|
||||
} catch (IllegalArgumentException e) {
|
||||
String msg = myFhirContext.getLocalizer().getMessage(RequestPartitionHelperService.class, "unknownPartitionId", thePartitionId.getPartitionId());
|
||||
throw new ResourceNotFoundException(msg);
|
||||
}
|
||||
return PartitionId.forPartitionNameAndId(partition.getName(), partition.getId(), thePartitionId.getPartitionDate());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// It's still possible that the partition only has a date but no name/id,
|
||||
// or it could just be null
|
||||
return thePartitionId;
|
||||
|
||||
}
|
||||
|
||||
private void validatePartition(@Nullable PartitionId thePartitionId, @Nonnull String theResourceName) {
|
||||
|
|
|
@ -20,49 +20,43 @@ package ca.uhn.fhir.jpa.partition;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
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.entity.PartitionEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.interceptor.model.PartitionId;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
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;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Interceptor
|
||||
public class RequestTenantPartitionInterceptor {
|
||||
|
||||
@Autowired
|
||||
private IPartitionConfigSvc myPartitionConfigSvc;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_CREATE)
|
||||
public PartitionablePartitionId PartitionIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
|
||||
public PartitionId PartitionIdentifyCreate(IBaseResource theResource, ServletRequestDetails theRequestDetails) {
|
||||
return extractPartitionIdFromRequest(theRequestDetails);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ)
|
||||
public PartitionablePartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) {
|
||||
public PartitionId PartitionIdentifyRead(ServletRequestDetails theRequestDetails) {
|
||||
return extractPartitionIdFromRequest(theRequestDetails);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PartitionablePartitionId extractPartitionIdFromRequest(ServletRequestDetails theRequestDetails) {
|
||||
String tenantId = theRequestDetails.getTenantId();
|
||||
private PartitionId extractPartitionIdFromRequest(ServletRequestDetails theRequestDetails) {
|
||||
|
||||
PartitionEntity partition;
|
||||
try {
|
||||
partition = myPartitionConfigSvc.getPartitionByName(tenantId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
String msg = myFhirContext.getLocalizer().getMessageSanitized(RequestTenantPartitionInterceptor.class, "unknownTenantName", tenantId);
|
||||
throw new ResourceNotFoundException(msg);
|
||||
// We will use the tenant ID that came from the request as the partition name
|
||||
String tenantId = theRequestDetails.getTenantId();
|
||||
if (isBlank(tenantId)) {
|
||||
throw new InternalErrorException("No tenant ID has been specified");
|
||||
}
|
||||
|
||||
PartitionablePartitionId retVal = new PartitionablePartitionId();
|
||||
retVal.setPartitionId(partition.getId());
|
||||
return retVal;
|
||||
return PartitionId.forPartitionName(tenantId);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1695,7 +1695,7 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
private void addCreatePartition(Integer thePartitionId, LocalDate thePartitionDate) {
|
||||
Validate.notNull(thePartitionId);
|
||||
PartitionId partitionId = new PartitionId(thePartitionId, thePartitionDate);
|
||||
PartitionId partitionId = PartitionId.forPartitionId(thePartitionId, thePartitionDate);
|
||||
myPartitionInterceptor.addCreatePartition(partitionId);
|
||||
}
|
||||
|
||||
|
@ -1704,20 +1704,20 @@ public class PartitioningR4Test extends BaseJpaR4SystemTest {
|
|||
}
|
||||
|
||||
private void addCreateNoPartitionId(LocalDate thePartitionDate) {
|
||||
PartitionId partitionId = new PartitionId(null, thePartitionDate);
|
||||
PartitionId partitionId = PartitionId.forPartitionId(null, thePartitionDate);
|
||||
myPartitionInterceptor.addCreatePartition(partitionId);
|
||||
}
|
||||
|
||||
private void addReadPartition(Integer thePartitionId) {
|
||||
PartitionId partitionId = null;
|
||||
if (thePartitionId != null) {
|
||||
partitionId = new PartitionId(thePartitionId, null);
|
||||
partitionId = PartitionId.forPartitionId(thePartitionId, null);
|
||||
}
|
||||
myPartitionInterceptor.addReadPartition(partitionId);
|
||||
}
|
||||
|
||||
private void addDefaultReadPartition() {
|
||||
PartitionId partitionId = new PartitionId(null, null);
|
||||
PartitionId partitionId = PartitionId.forPartitionId(null, null);
|
||||
myPartitionInterceptor.addReadPartition(partitionId);
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ public class MultitenantServerR4Test extends BaseResourceProviderR4Test {
|
|||
ourClient.create().resource(patientA).execute();
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertThat(e.getMessage(), containsString("Unknown tenant: TENANT-ZZZ"));
|
||||
assertThat(e.getMessage(), containsString("Unknown partition name: TENANT-ZZZ"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -81,6 +81,6 @@ public class PartitionablePartitionId implements Cloneable {
|
|||
}
|
||||
|
||||
public PartitionId toPartitionId() {
|
||||
return new PartitionId(getPartitionId(), getPartitionDate());
|
||||
return PartitionId.forPartitionId(getPartitionId(), getPartitionDate());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue