001/*
002 * #%L
003 * HAPI FHIR Storage api
004 * %%
005 * Copyright (C) 2014 - 2025 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.provider;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
025import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
026import ca.uhn.fhir.jpa.api.model.ExpungeOutcome;
027import ca.uhn.fhir.jpa.model.util.JpaConstants;
028import ca.uhn.fhir.rest.api.Constants;
029import ca.uhn.fhir.rest.param.DateRangeParam;
030import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
031import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
032import ca.uhn.fhir.util.ParametersUtil;
033import jakarta.servlet.http.HttpServletRequest;
034import org.apache.commons.lang3.StringUtils;
035import org.hl7.fhir.instance.model.api.IBaseParameters;
036import org.hl7.fhir.instance.model.api.IPrimitiveType;
037import org.jboss.logging.MDC;
038import org.springframework.beans.factory.annotation.Autowired;
039
040import java.util.Date;
041import java.util.Enumeration;
042import java.util.Set;
043import java.util.TreeSet;
044
045public abstract class BaseJpaProvider {
046        public static final String REMOTE_ADDR = "req.remoteAddr";
047        public static final String REMOTE_UA = "req.userAgent";
048        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaProvider.class);
049
050        @Autowired
051        protected JpaStorageSettings myStorageSettings;
052
053        @Autowired
054        private FhirContext myContext;
055
056        public BaseJpaProvider() {
057                super();
058        }
059
060        public void setStorageSettingsForUnitTest(JpaStorageSettings theStorageSettings) {
061                myStorageSettings = theStorageSettings;
062        }
063
064        protected ExpungeOptions createExpungeOptions(
065                        IPrimitiveType<? extends Integer> theLimit,
066                        IPrimitiveType<? extends Boolean> theExpungeDeletedResources,
067                        IPrimitiveType<? extends Boolean> theExpungeOldVersions,
068                        IPrimitiveType<? extends Boolean> theExpungeEverything) {
069                ExpungeOptions options = new ExpungeOptions();
070                if (theLimit != null && theLimit.getValue() != null) {
071                        options.setLimit(theLimit.getValue());
072                }
073
074                if (theExpungeOldVersions != null && theExpungeOldVersions.getValue() != null) {
075                        options.setExpungeOldVersions(theExpungeOldVersions.getValue());
076                }
077
078                if (theExpungeDeletedResources != null && theExpungeDeletedResources.getValue() != null) {
079                        options.setExpungeDeletedResources(theExpungeDeletedResources.getValue());
080                }
081
082                if (theExpungeEverything != null && theExpungeEverything.getValue() != null) {
083                        options.setExpungeEverything(theExpungeEverything.getValue());
084                }
085                return options;
086        }
087
088        protected IBaseParameters createExpungeResponse(ExpungeOutcome theOutcome) {
089                IBaseParameters parameters = ParametersUtil.newInstance(getContext());
090                String value = Integer.toString(theOutcome.getDeletedCount());
091                ParametersUtil.addParameterToParameters(
092                                getContext(), parameters, JpaConstants.OPERATION_EXPUNGE_OUT_PARAM_EXPUNGE_COUNT, "integer", value);
093                return parameters;
094        }
095
096        public void endRequest(ServletRequestDetails theRequest) {
097                endRequest(theRequest.getServletRequest());
098        }
099
100        public FhirContext getContext() {
101                return myContext;
102        }
103
104        public void setContext(FhirContext theContext) {
105                myContext = theContext;
106        }
107
108        protected DateRangeParam processSinceOrAt(Date theSince, DateRangeParam theAt) {
109                boolean haveAt =
110                                theAt != null && (theAt.getLowerBoundAsInstant() != null || theAt.getUpperBoundAsInstant() != null);
111                if (haveAt && theSince != null) {
112                        String msg = getContext().getLocalizer().getMessage(BaseJpaProvider.class, "cantCombintAtAndSince");
113                        throw new InvalidRequestException(Msg.code(553) + msg);
114                }
115
116                if (haveAt) {
117                        return theAt;
118                }
119
120                return new DateRangeParam(theSince, null);
121        }
122
123        /**
124         * @param theRequest The servlet request
125         */
126        public static void endRequest(HttpServletRequest theRequest) {
127                MDC.remove(REMOTE_ADDR);
128                MDC.remove(REMOTE_UA);
129        }
130
131        public static void startRequest(HttpServletRequest theRequest) {
132                if (theRequest == null) {
133                        return;
134                }
135
136                if (ourLog.isDebugEnabled()) {
137                        Set<String> headerNames = new TreeSet<>();
138                        for (Enumeration<String> enums = theRequest.getHeaderNames(); enums.hasMoreElements(); ) {
139                                headerNames.add(enums.nextElement());
140                        }
141                        ourLog.debug("Request headers: {}", headerNames);
142                }
143
144                Enumeration<String> forwardedFors = theRequest.getHeaders(Constants.HEADER_X_FORWARDED_FOR);
145                StringBuilder b = new StringBuilder();
146                for (; forwardedFors != null && forwardedFors.hasMoreElements(); ) {
147                        if (b.length() > 0) {
148                                b.append(" / ");
149                        }
150                        b.append(forwardedFors.nextElement());
151                }
152
153                String forwardedFor = b.toString();
154                String ip = theRequest.getRemoteAddr();
155                if (StringUtils.isBlank(forwardedFor)) {
156                        org.slf4j.MDC.put(REMOTE_ADDR, ip);
157                        ourLog.debug("Request is from address: {}", ip);
158                } else {
159                        org.slf4j.MDC.put(REMOTE_ADDR, forwardedFor);
160                        ourLog.debug("Request is from forwarded address: {}", forwardedFor);
161                }
162
163                String userAgent = StringUtils.defaultString(theRequest.getHeader("user-agent"));
164                org.slf4j.MDC.put(REMOTE_UA, userAgent);
165        }
166
167        public static void startRequest(ServletRequestDetails theRequest) {
168                startRequest(theRequest.getServletRequest());
169        }
170}