001/*
002 * #%L
003 * HAPI FHIR JPA Server
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.context.support.IValidationSupport;
024import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
025import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
026import ca.uhn.fhir.rest.api.Constants;
027import ca.uhn.fhir.rest.server.RestfulServer;
028import ca.uhn.fhir.rest.server.provider.ServerCapabilityStatementProvider;
029import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
030import ca.uhn.fhir.util.CoverageIgnore;
031import ca.uhn.fhir.util.ExtensionConstants;
032import ca.uhn.fhir.util.ExtensionUtil;
033import ca.uhn.fhir.util.FhirTerser;
034import jakarta.annotation.Nonnull;
035import org.apache.commons.lang3.Validate;
036import org.hl7.fhir.instance.model.api.IBase;
037import org.hl7.fhir.instance.model.api.IBaseConformance;
038import org.hl7.fhir.r4.model.Bundle;
039import org.hl7.fhir.r4.model.CapabilityStatement.ConditionalDeleteStatus;
040import org.hl7.fhir.r4.model.CapabilityStatement.ResourceVersionPolicy;
041import org.hl7.fhir.r4.model.Meta;
042
043import java.util.Map;
044
045import static ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT;
046import static ca.uhn.fhir.rest.api.Constants.PARAM_FILTER;
047import static ca.uhn.fhir.rest.api.Constants.PARAM_LANGUAGE;
048import static ca.uhn.fhir.rest.api.Constants.PARAM_TEXT;
049import static org.apache.commons.lang3.StringUtils.isNotBlank;
050
051/**
052 * R4+ Only
053 */
054public class JpaCapabilityStatementProvider extends ServerCapabilityStatementProvider {
055
056        private final FhirContext myContext;
057        private final ISearchParamRegistry mySearchParamRegistry;
058        private JpaStorageSettings myStorageSettings;
059        private String myImplementationDescription;
060        private boolean myIncludeResourceCounts;
061        private IFhirSystemDao<?, ?> mySystemDao;
062
063        /**
064         * Constructor
065         */
066        public JpaCapabilityStatementProvider(
067                        @Nonnull RestfulServer theRestfulServer,
068                        @Nonnull IFhirSystemDao<?, ?> theSystemDao,
069                        @Nonnull JpaStorageSettings theStorageSettings,
070                        @Nonnull ISearchParamRegistry theSearchParamRegistry,
071                        IValidationSupport theValidationSupport) {
072                super(theRestfulServer, theSearchParamRegistry, theValidationSupport);
073
074                Validate.notNull(theRestfulServer, "theRestfulServer must not be null");
075                Validate.notNull(theSystemDao, "theSystemDao must not be null");
076                Validate.notNull(theStorageSettings, "theStorageSettings must not be null");
077                Validate.notNull(theSearchParamRegistry, "theSearchParamRegistry must not be null");
078
079                myContext = theRestfulServer.getFhirContext();
080                mySystemDao = theSystemDao;
081                myStorageSettings = theStorageSettings;
082                mySearchParamRegistry = theSearchParamRegistry;
083
084                setIncludeResourceCounts(true);
085        }
086
087        @Override
088        protected void postProcess(FhirTerser theTerser, IBaseConformance theCapabilityStatement) {
089                super.postProcess(theTerser, theCapabilityStatement);
090
091                if (isNotBlank(myImplementationDescription)) {
092                        theTerser.setElement(theCapabilityStatement, "implementation.description", myImplementationDescription);
093                }
094
095                theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_FHIR_JSON_NEW);
096                theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_FHIR_XML_NEW);
097                theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_JSON_PATCH);
098                theTerser.addElement(theCapabilityStatement, "patchFormat", Constants.CT_XML_PATCH);
099        }
100
101        @Override
102        protected void postProcessRestResource(FhirTerser theTerser, IBase theResource, String theResourceName) {
103                super.postProcessRestResource(theTerser, theResource, theResourceName);
104
105                theTerser.addElement(theResource, "versioning", ResourceVersionPolicy.VERSIONEDUPDATE.toCode());
106
107                if (myStorageSettings.isAllowMultipleDelete()) {
108                        theTerser.addElement(theResource, "conditionalDelete", ConditionalDeleteStatus.MULTIPLE.toCode());
109                } else {
110                        theTerser.addElement(theResource, "conditionalDelete", ConditionalDeleteStatus.SINGLE.toCode());
111                }
112
113                // Add resource counts
114                if (myIncludeResourceCounts) {
115                        Map<String, Long> counts = mySystemDao.getResourceCountsFromCache();
116                        if (counts != null) {
117                                Long count = counts.get(theResourceName);
118                                if (count != null) {
119                                        ExtensionUtil.setExtension(
120                                                        myContext,
121                                                        theResource,
122                                                        ExtensionConstants.CONF_RESOURCE_COUNT,
123                                                        "decimal",
124                                                        Long.toString(count));
125                                }
126                        }
127                }
128        }
129
130        public boolean isIncludeResourceCounts() {
131                return myIncludeResourceCounts;
132        }
133
134        public void setIncludeResourceCounts(boolean theIncludeResourceCounts) {
135                myIncludeResourceCounts = theIncludeResourceCounts;
136        }
137
138        public void setStorageSettings(JpaStorageSettings theStorageSettings) {
139                this.myStorageSettings = theStorageSettings;
140        }
141
142        @CoverageIgnore
143        public void setImplementationDescription(String theImplDesc) {
144                myImplementationDescription = theImplDesc;
145        }
146
147        @CoverageIgnore
148        public void setSystemDao(IFhirSystemDao<Bundle, Meta> mySystemDao) {
149                this.mySystemDao = mySystemDao;
150        }
151
152        @Override
153        protected boolean searchParamEnabled(String theResourceName, String theSearchParam) {
154                return switch (theSearchParam) {
155                        case PARAM_FILTER -> myStorageSettings.isFilterParameterEnabled();
156                        case PARAM_CONTENT, PARAM_TEXT, PARAM_LANGUAGE -> mySearchParamRegistry.hasActiveSearchParam(
157                                        theResourceName, theSearchParam, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH);
158                        default -> true;
159                };
160        }
161}