Tenancy docs

This commit is contained in:
jamesagnew 2020-04-19 10:15:51 -04:00
parent 6c760c9bff
commit cd3e1c5e72
8 changed files with 112 additions and 37 deletions

View File

@ -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);
}
}

View File

@ -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}

View File

@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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"));
}
}

View File

@ -81,6 +81,6 @@ public class PartitionablePartitionId implements Cloneable {
}
public PartitionId toPartitionId() {
return new PartitionId(getPartitionId(), getPartitionDate());
return PartitionId.forPartitionId(getPartitionId(), getPartitionDate());
}
}