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}