001/*- 002 * #%L 003 * Smile CDR - CDR 004 * %% 005 * Copyright (C) 2016 - 2025 Smile CDR, Inc. 006 * %% 007 * All rights reserved. 008 * #L% 009 */ 010package ca.cdr.api.security.permission; 011 012import com.google.common.collect.Sets; 013import jakarta.annotation.Nonnull; 014import org.apache.commons.lang3.StringUtils; 015import org.hl7.fhir.instance.model.api.IIdType; 016import org.hl7.fhir.instance.model.api.IPrimitiveType; 017 018import java.util.Arrays; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Optional; 024import java.util.stream.Collectors; 025 026/** 027 * types within compartments. 028 * E.g. <code>Patient/123 Patient Observation</code> 029 * E.g. <code>Patient/123 *</code> 030 * E.g. <code>Patient/123,Patient/456 Patient Encounter</code> 031 * E.g. <code>Patient/123,Patient/456 *</code> 032 * <p> 033 * NOTE: A types list consisting of a single '*' means all types. 034 */ 035public final class CompartmentsWithExportResourceTypes extends PermissionArgumentValue 036 implements PermissionArgumentFormat.ICompartmentsWithExportResourceTypesRestriction { 037 038 private static final String RESOURCE_TYPES_ALL = "*"; 039 private static final String OWNER_LIST_DELIMITER = ","; 040 private static final String RESOURCE_TYPE_LIST_DELIMITER = " "; 041 042 private final Collection<IIdType> myOwners; 043 private final LinkedHashSet<String> myResourceTypes; 044 private final boolean myCanExportAllResourceTypes; 045 046 private CompartmentsWithExportResourceTypes( 047 Collection<IIdType> theOwners, boolean theCanExportAllResourceTypes, Iterable<String> theResourceTypes) { 048 myOwners = theOwners; 049 myCanExportAllResourceTypes = theCanExportAllResourceTypes; 050 myResourceTypes = Sets.newLinkedHashSet(theResourceTypes); 051 } 052 053 public static CompartmentsWithExportResourceTypes build( 054 Collection<IIdType> theOwners, boolean theCanExportAllResourceTypes, Collection<String> theResourceTypes) { 055 return new CompartmentsWithExportResourceTypes(theOwners, theCanExportAllResourceTypes, theResourceTypes); 056 } 057 058 public static CompartmentsWithExportResourceTypes withTypes( 059 Collection<IIdType> theOwners, Iterable<String> theResourceTypes) { 060 return new CompartmentsWithExportResourceTypes(theOwners, false, theResourceTypes); 061 } 062 063 @Override 064 public Collection<IIdType> getOwners() { 065 return myOwners; 066 } 067 068 @Override 069 public boolean canExportAllResourceTypes() { 070 return myCanExportAllResourceTypes; 071 } 072 073 @Override 074 public Collection<String> getResourceTypes() { 075 return myResourceTypes; 076 } 077 078 @Override 079 @Nonnull 080 String getStringValue() { 081 final StringBuilder stringBuilder = new StringBuilder(myOwners.stream() 082 .map(IPrimitiveType::getValueAsString) 083 .collect(Collectors.joining(OWNER_LIST_DELIMITER))); 084 085 if (myCanExportAllResourceTypes || !myResourceTypes.isEmpty()) { 086 stringBuilder.append(RESOURCE_TYPE_LIST_DELIMITER); 087 if (myCanExportAllResourceTypes) { 088 stringBuilder.append(RESOURCE_TYPES_ALL); 089 } else if (!myResourceTypes.isEmpty()) { 090 stringBuilder.append(StringUtils.join(myResourceTypes, RESOURCE_TYPE_LIST_DELIMITER)); 091 } 092 } 093 094 return stringBuilder.toString(); 095 } 096 097 static final class Format implements PermissionArgumentFormat<CompartmentsWithExportResourceTypes> { 098 099 @Override 100 public Class<CompartmentsWithExportResourceTypes> getType() { 101 return CompartmentsWithExportResourceTypes.class; 102 } 103 104 @Override 105 public @Nonnull Optional<CompartmentsWithExportResourceTypes> parse( 106 String theArgumentString, @Nonnull IdBuilder theIdBuilder) { 107 if (StringUtils.isBlank(theArgumentString)) { 108 return Optional.empty(); 109 } 110 111 final String[] splitByResourceDelim = theArgumentString.split(RESOURCE_TYPE_LIST_DELIMITER); 112 113 if (splitByResourceDelim.length <= 1) { // There are no resource types 114 return Optional.empty(); 115 } 116 117 final String[] splitByIdDelim = splitByResourceDelim[0].split(OWNER_LIST_DELIMITER); 118 119 final List<String> owners = Arrays.stream(splitByIdDelim).toList(); 120 final List<String> resourceTypes = Arrays.stream(splitByResourceDelim, 1, splitByResourceDelim.length) 121 .toList(); 122 123 if (resourceTypes.contains(RESOURCE_TYPES_ALL) && splitByResourceDelim.length > 2) { 124 return Optional.empty(); // Resource Types of * must not be followed by anything else 125 } 126 127 if (owners.stream() 128 .allMatch(idString -> 129 PermissionArgumentFormat.ID_FORMAT.matcher(idString).matches())) { 130 final List<IIdType> ownersAsIds = 131 owners.stream().map(theIdBuilder).toList(); 132 133 if (Collections.singletonList(RESOURCE_TYPES_ALL).equals(resourceTypes)) { 134 return Optional.of( 135 new CompartmentsWithExportResourceTypes(ownersAsIds, true, Collections.emptyList())); 136 } 137 return Optional.of(new CompartmentsWithExportResourceTypes(ownersAsIds, false, resourceTypes)); 138 } 139 140 return Optional.empty(); 141 } 142 143 @Override 144 public @Nonnull String format(@Nonnull CompartmentsWithExportResourceTypes theArgument) { 145 return theArgument.getStringValue(); 146 } 147 } 148}