001/*- 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.interceptor.model; 021 022import ca.uhn.fhir.model.api.IModelJson; 023import ca.uhn.fhir.util.JsonUtil; 024import com.fasterxml.jackson.annotation.JsonProperty; 025import com.fasterxml.jackson.core.JsonProcessingException; 026import com.fasterxml.jackson.databind.ObjectMapper; 027import jakarta.annotation.Nonnull; 028import jakarta.annotation.Nullable; 029import org.apache.commons.lang3.Validate; 030import org.apache.commons.lang3.builder.EqualsBuilder; 031import org.apache.commons.lang3.builder.HashCodeBuilder; 032import org.apache.commons.lang3.builder.ToStringBuilder; 033import org.apache.commons.lang3.builder.ToStringStyle; 034 035import java.time.LocalDate; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collection; 039import java.util.Collections; 040import java.util.List; 041import java.util.Objects; 042import java.util.stream.Collectors; 043import java.util.stream.Stream; 044 045import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; 046 047/** 048 * @since 5.0.0 049 */ 050public class RequestPartitionId implements IModelJson { 051 private static final RequestPartitionId ALL_PARTITIONS = new RequestPartitionId(); 052 private static final ObjectMapper ourObjectMapper = 053 new ObjectMapper().registerModule(new com.fasterxml.jackson.datatype.jsr310.JavaTimeModule()); 054 055 @JsonProperty("partitionDate") 056 private final LocalDate myPartitionDate; 057 058 @JsonProperty("allPartitions") 059 private final boolean myAllPartitions; 060 061 @JsonProperty("partitionIds") 062 private final List<Integer> myPartitionIds; 063 064 @JsonProperty("partitionNames") 065 private final List<String> myPartitionNames; 066 067 /** 068 * Constructor for a single partition 069 */ 070 private RequestPartitionId( 071 @Nullable String thePartitionName, @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { 072 myPartitionIds = toListOrNull(thePartitionId); 073 myPartitionNames = toListOrNull(thePartitionName); 074 myPartitionDate = thePartitionDate; 075 myAllPartitions = false; 076 } 077 078 /** 079 * Constructor for a multiple partition 080 */ 081 private RequestPartitionId( 082 @Nullable List<String> thePartitionName, 083 @Nullable List<Integer> thePartitionId, 084 @Nullable LocalDate thePartitionDate) { 085 myPartitionIds = toListOrNull(thePartitionId); 086 myPartitionNames = toListOrNull(thePartitionName); 087 myPartitionDate = thePartitionDate; 088 myAllPartitions = false; 089 } 090 091 /** 092 * Constructor for all partitions 093 */ 094 private RequestPartitionId() { 095 super(); 096 myPartitionDate = null; 097 myPartitionNames = null; 098 myPartitionIds = null; 099 myAllPartitions = true; 100 } 101 102 /** 103 * Creates a new RequestPartitionId which includes all partition IDs from 104 * this {@link RequestPartitionId} but also includes all IDs from the given 105 * {@link RequestPartitionId}. Any duplicates are only included once, and 106 * partition names and dates are ignored and not returned. This {@link RequestPartitionId} 107 * and {@literal theOther} are not modified. 108 * 109 * @since 7.4.0 110 */ 111 public RequestPartitionId mergeIds(RequestPartitionId theOther) { 112 if (isAllPartitions() || theOther.isAllPartitions()) { 113 return RequestPartitionId.allPartitions(); 114 } 115 116 // don't know why this is required - otherwise PartitionedStrictTransactionR4Test fails 117 if (this.equals(theOther)) { 118 return this; 119 } 120 121 List<Integer> thisPartitionIds = getPartitionIds(); 122 List<Integer> otherPartitionIds = theOther.getPartitionIds(); 123 List<Integer> newPartitionIds = Stream.concat(thisPartitionIds.stream(), otherPartitionIds.stream()) 124 .distinct() 125 .collect(Collectors.toList()); 126 return RequestPartitionId.fromPartitionIds(newPartitionIds); 127 } 128 129 public static RequestPartitionId fromJson(String theJson) throws JsonProcessingException { 130 return ourObjectMapper.readValue(theJson, RequestPartitionId.class); 131 } 132 133 public boolean isAllPartitions() { 134 return myAllPartitions; 135 } 136 137 public boolean isPartitionCovered(Integer thePartitionId) { 138 return isAllPartitions() || getPartitionIds().contains(thePartitionId); 139 } 140 141 @Nullable 142 public LocalDate getPartitionDate() { 143 return myPartitionDate; 144 } 145 146 @Nullable 147 public List<String> getPartitionNames() { 148 return myPartitionNames; 149 } 150 151 @Nonnull 152 public List<Integer> getPartitionIds() { 153 Validate.notNull(myPartitionIds, "Partition IDs have not been set"); 154 return myPartitionIds; 155 } 156 157 @Override 158 public String toString() { 159 ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); 160 if (hasPartitionIds()) { 161 b.append("ids", getPartitionIds()); 162 } 163 if (hasPartitionNames()) { 164 b.append("names", getPartitionNames()); 165 } 166 if (myAllPartitions) { 167 b.append("allPartitions", myAllPartitions); 168 } 169 return b.build(); 170 } 171 172 @Override 173 public boolean equals(Object theO) { 174 if (this == theO) { 175 return true; 176 } 177 178 if (theO == null || getClass() != theO.getClass()) { 179 return false; 180 } 181 182 RequestPartitionId that = (RequestPartitionId) theO; 183 184 EqualsBuilder b = new EqualsBuilder(); 185 b.append(myAllPartitions, that.myAllPartitions); 186 b.append(myPartitionDate, that.myPartitionDate); 187 b.append(myPartitionIds, that.myPartitionIds); 188 b.append(myPartitionNames, that.myPartitionNames); 189 return b.isEquals(); 190 } 191 192 @Override 193 public int hashCode() { 194 return new HashCodeBuilder(17, 37) 195 .append(myPartitionDate) 196 .append(myAllPartitions) 197 .append(myPartitionIds) 198 .append(myPartitionNames) 199 .toHashCode(); 200 } 201 202 public String toJson() { 203 return JsonUtil.serializeOrInvalidRequest(this); 204 } 205 206 @Nullable 207 public Integer getFirstPartitionIdOrNull() { 208 if (myPartitionIds != null) { 209 return myPartitionIds.get(0); 210 } 211 return null; 212 } 213 214 public String getFirstPartitionNameOrNull() { 215 if (myPartitionNames != null) { 216 return myPartitionNames.get(0); 217 } 218 return null; 219 } 220 221 /** 222 * Returns true if this request partition contains only one partition ID and it is the DEFAULT partition ID (null) 223 */ 224 public boolean isDefaultPartition() { 225 if (isAllPartitions()) { 226 return false; 227 } 228 return hasPartitionIds() 229 && getPartitionIds().size() == 1 230 && getPartitionIds().get(0) == null; 231 } 232 233 public boolean hasPartitionId(Integer thePartitionId) { 234 Validate.notNull(myPartitionIds, "Partition IDs not set"); 235 return myPartitionIds.contains(thePartitionId); 236 } 237 238 public boolean hasPartitionIds() { 239 return myPartitionIds != null; 240 } 241 242 public boolean hasPartitionNames() { 243 return myPartitionNames != null; 244 } 245 246 public boolean hasDefaultPartitionId() { 247 return getPartitionIds().contains(null); 248 } 249 250 public List<Integer> getPartitionIdsWithoutDefault() { 251 return getPartitionIds().stream().filter(Objects::nonNull).collect(Collectors.toList()); 252 } 253 254 @Nullable 255 private static <T> List<T> toListOrNull(@Nullable Collection<T> theList) { 256 if (theList != null) { 257 if (theList.size() == 1) { 258 return Collections.singletonList(theList.iterator().next()); 259 } 260 return Collections.unmodifiableList(new ArrayList<>(theList)); 261 } 262 return null; 263 } 264 265 @Nullable 266 private static <T> List<T> toListOrNull(@Nullable T theObject) { 267 if (theObject != null) { 268 return Collections.singletonList(theObject); 269 } 270 return null; 271 } 272 273 @SafeVarargs 274 @Nullable 275 private static <T> List<T> toListOrNull(@Nullable T... theObject) { 276 if (theObject != null) { 277 return Arrays.asList(theObject); 278 } 279 return null; 280 } 281 282 @Nonnull 283 public static RequestPartitionId allPartitions() { 284 return ALL_PARTITIONS; 285 } 286 287 @Nonnull 288 public static RequestPartitionId defaultPartition() { 289 return fromPartitionIds(Collections.singletonList(null)); 290 } 291 292 @Nonnull 293 public static RequestPartitionId defaultPartition(@Nullable LocalDate thePartitionDate) { 294 return fromPartitionIds(Collections.singletonList(null), thePartitionDate); 295 } 296 297 @Nonnull 298 public static RequestPartitionId fromPartitionId(@Nullable Integer thePartitionId) { 299 return fromPartitionIds(Collections.singletonList(thePartitionId)); 300 } 301 302 @Nonnull 303 public static RequestPartitionId fromPartitionId( 304 @Nullable Integer thePartitionId, @Nullable LocalDate thePartitionDate) { 305 return new RequestPartitionId(null, Collections.singletonList(thePartitionId), thePartitionDate); 306 } 307 308 @Nonnull 309 public static RequestPartitionId fromPartitionIds(@Nonnull Collection<Integer> thePartitionIds) { 310 return fromPartitionIds(thePartitionIds, null); 311 } 312 313 @Nonnull 314 public static RequestPartitionId fromPartitionIds( 315 @Nonnull Collection<Integer> thePartitionIds, @Nullable LocalDate thePartitionDate) { 316 return new RequestPartitionId(null, toListOrNull(thePartitionIds), thePartitionDate); 317 } 318 319 @Nonnull 320 public static RequestPartitionId fromPartitionIds(Integer... thePartitionIds) { 321 return new RequestPartitionId(null, toListOrNull(thePartitionIds), null); 322 } 323 324 @Nonnull 325 public static RequestPartitionId fromPartitionName(@Nullable String thePartitionName) { 326 return fromPartitionName(thePartitionName, null); 327 } 328 329 @Nonnull 330 public static RequestPartitionId fromPartitionName( 331 @Nullable String thePartitionName, @Nullable LocalDate thePartitionDate) { 332 return new RequestPartitionId(thePartitionName, null, thePartitionDate); 333 } 334 335 @Nonnull 336 public static RequestPartitionId fromPartitionNames(@Nullable List<String> thePartitionNames) { 337 return new RequestPartitionId(toListOrNull(thePartitionNames), null, null); 338 } 339 340 @Nonnull 341 public static RequestPartitionId fromPartitionNames(String... thePartitionNames) { 342 return new RequestPartitionId(toListOrNull(thePartitionNames), null, null); 343 } 344 345 @Nonnull 346 public static RequestPartitionId fromPartitionIdAndName( 347 @Nullable Integer thePartitionId, @Nullable String thePartitionName) { 348 return new RequestPartitionId(thePartitionName, thePartitionId, null); 349 } 350 351 @Nonnull 352 public static RequestPartitionId forPartitionIdAndName( 353 @Nullable Integer thePartitionId, @Nullable String thePartitionName, @Nullable LocalDate thePartitionDate) { 354 return new RequestPartitionId(thePartitionName, thePartitionId, thePartitionDate); 355 } 356 357 @Nonnull 358 public static RequestPartitionId forPartitionIdsAndNames( 359 List<String> thePartitionNames, List<Integer> thePartitionIds, LocalDate thePartitionDate) { 360 return new RequestPartitionId(thePartitionNames, thePartitionIds, thePartitionDate); 361 } 362 363 public static boolean isDefaultPartition(@Nullable RequestPartitionId thePartitionId) { 364 if (thePartitionId == null) { 365 return false; 366 } 367 368 return thePartitionId.isDefaultPartition(); 369 } 370 371 /** 372 * Create a string representation suitable for use as a cache key. Null aware. 373 * <p> 374 * Returns the partition IDs (numeric) as a joined string with a space between, using the string "null" for any null values 375 */ 376 public static String stringifyForKey(@Nonnull RequestPartitionId theRequestPartitionId) { 377 String retVal = "(all)"; 378 if (!theRequestPartitionId.isAllPartitions()) { 379 assert theRequestPartitionId.hasPartitionIds(); 380 retVal = theRequestPartitionId.getPartitionIds().stream() 381 .map(t -> defaultIfNull(t, "null").toString()) 382 .collect(Collectors.joining(" ")); 383 } 384 return retVal; 385 } 386 387 public String asJson() throws JsonProcessingException { 388 return ourObjectMapper.writeValueAsString(this); 389 } 390}