001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.jpa.interceptor;
021
022import ca.uhn.fhir.interceptor.api.Hook;
023import ca.uhn.fhir.interceptor.api.Interceptor;
024import ca.uhn.fhir.interceptor.api.Pointcut;
025import ca.uhn.fhir.jpa.api.model.ResourceVersionConflictResolutionStrategy;
026import ca.uhn.fhir.rest.api.server.RequestDetails;
027import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
028import org.apache.commons.lang3.Validate;
029
030/**
031 * This interceptor looks for a header on incoming requests called <code>X-Retry-On-Version-Conflict</code> and
032 * if present, it will instruct the server to automatically retry JPA server operations that would have
033 * otherwise failed with a {@link ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException} (HTTP 409).
034 * <p>
035 * The format of the header is:<br/>
036 * <code>X-Retry-On-Version-Conflict: retry; max-retries=100</code>
037 * </p>
038 */
039@Interceptor
040public class UserRequestRetryVersionConflictsInterceptor {
041
042        /** Deprecated and moved to {@link ca.uhn.fhir.rest.api.Constants#HEADER_RETRY_ON_VERSION_CONFLICT} */
043        @Deprecated
044        public static final String HEADER_NAME = "X-Retry-On-Version-Conflict";
045
046        /** Deprecated and moved to {@link ca.uhn.fhir.rest.api.Constants#HEADER_MAX_RETRIES} */
047        @Deprecated
048        public static final String MAX_RETRIES = "max-retries";
049
050        /** Deprecated and moved to {@link ca.uhn.fhir.rest.api.Constants#HEADER_RETRY} */
051        @Deprecated
052        public static final String RETRY = "retry";
053
054        @Hook(value = Pointcut.STORAGE_VERSION_CONFLICT, order = 100)
055        public ResourceVersionConflictResolutionStrategy check(RequestDetails theRequestDetails) {
056                ResourceVersionConflictResolutionStrategy retVal = new ResourceVersionConflictResolutionStrategy();
057                boolean shouldSetRetries = theRequestDetails != null && theRequestDetails.isRetry();
058                if (shouldSetRetries) {
059                        retVal.setRetry(true);
060                        int maxRetries = Math.min(100, theRequestDetails.getMaxRetries());
061                        retVal.setMaxRetries(maxRetries);
062                }
063
064                return retVal;
065        }
066
067        /**
068         * Convenience method to add a retry header to a system request
069         */
070        public static void addRetryHeader(SystemRequestDetails theRequestDetails, int theMaxRetries) {
071                Validate.inclusiveBetween(1, Integer.MAX_VALUE, theMaxRetries, "Max retries must be > 0");
072                theRequestDetails.setRetry(true);
073                theRequestDetails.setMaxRetries(theMaxRetries);
074        }
075}