001package ca.uhn.fhir.jpa.interceptor;
002
003/*-
004 * #%L
005 * HAPI FHIR Storage api
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.interceptor.api.Hook;
024import ca.uhn.fhir.interceptor.api.Interceptor;
025import ca.uhn.fhir.interceptor.api.Pointcut;
026import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
027import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
028import ca.uhn.fhir.rest.api.server.RequestDetails;
029import org.apache.commons.lang3.Validate;
030
031import java.util.List;
032import java.util.StringTokenizer;
033
034import static org.apache.commons.lang3.StringUtils.isNotBlank;
035import static org.apache.commons.lang3.StringUtils.trim;
036
037/**
038 * This interceptor looks for a header on incoming requests called <code>X-Retry-On-Version-Conflict</code> and
039 * if present, it will instruct the server to automatically retry JPA server operations that would have
040 * otherwise failed with a {@link ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException} (HTTP 409).
041 * <p>
042 * The format of the header is:<br/>
043 * <code>X-Retry-On-Version-Conflict: retry; max-retries=100</code>
044 * </p>
045 */
046@Interceptor
047public class UserRequestRetryVersionConflictsInterceptor {
048
049        public static final String HEADER_NAME = "X-Retry-On-Version-Conflict";
050        public static final String MAX_RETRIES = "max-retries";
051        public static final String RETRY = "retry";
052
053        @Hook(value = Pointcut.STORAGE_VERSION_CONFLICT, order = 100)
054        public ResourceVersionConflictResolutionStrategy check(RequestDetails theRequestDetails) {
055                ResourceVersionConflictResolutionStrategy retVal = new ResourceVersionConflictResolutionStrategy();
056
057                if (theRequestDetails != null) {
058                        List<String> headers = theRequestDetails.getHeaders(HEADER_NAME);
059                        if (headers != null) {
060                                for (String headerValue : headers) {
061                                        if (isNotBlank(headerValue)) {
062
063                                                StringTokenizer tok = new StringTokenizer(headerValue, ";");
064                                                while (tok.hasMoreTokens()) {
065                                                        String next = trim(tok.nextToken());
066                                                        if (next.equals(RETRY)) {
067                                                                retVal.setRetry(true);
068                                                        } else if (next.startsWith(MAX_RETRIES + "=")) {
069
070                                                                String val = trim(next.substring((MAX_RETRIES + "=").length()));
071                                                                int maxRetries = Integer.parseInt(val);
072                                                                maxRetries = Math.min(100, maxRetries);
073                                                                retVal.setMaxRetries(maxRetries);
074
075                                                        }
076
077                                                }
078
079                                        }
080                                }
081                        }
082                }
083
084                return retVal;
085        }
086
087
088        /**
089         * Convenience method to add a retry header to a system request
090         */
091        public static void addRetryHeader(SystemRequestDetails theRequestDetails, int theMaxRetries) {
092                Validate.inclusiveBetween(1, Integer.MAX_VALUE, theMaxRetries, "Max retries must be > 0");
093                String value = RETRY + "; " + MAX_RETRIES + "=" + theMaxRetries;
094                theRequestDetails.addHeader(HEADER_NAME, value);
095        }
096}