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.api.dao;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.jpa.api.IDaoRegistry;
026import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
027import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
028import jakarta.annotation.Nullable;
029import org.apache.commons.lang3.Validate;
030import org.hl7.fhir.instance.model.api.IBaseResource;
031import org.springframework.beans.BeansException;
032import org.springframework.beans.factory.annotation.Autowired;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.ApplicationContextAware;
035
036import java.util.Arrays;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.HashSet;
041import java.util.List;
042import java.util.Map;
043import java.util.Set;
044import java.util.stream.Collectors;
045
046public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
047        private ApplicationContext myAppCtx;
048
049        @Autowired
050        private FhirContext myContext;
051
052        private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao;
053        private volatile IFhirSystemDao<?, ?> mySystemDao;
054        private Set<String> mySupportedResourceTypes;
055
056        /**
057         * Constructor
058         */
059        public DaoRegistry() {
060                this(null);
061        }
062
063        /**
064         * Constructor
065         */
066        public DaoRegistry(FhirContext theFhirContext) {
067                super();
068                myContext = theFhirContext;
069        }
070
071        public void setSupportedResourceTypes(Collection<String> theSupportedResourceTypes) {
072                HashSet<String> supportedResourceTypes = new HashSet<>();
073                if (theSupportedResourceTypes != null) {
074                        supportedResourceTypes.addAll(theSupportedResourceTypes);
075                }
076                mySupportedResourceTypes = supportedResourceTypes;
077                myResourceNameToResourceDao = null;
078        }
079
080        @Override
081        public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
082                myAppCtx = theApplicationContext;
083        }
084
085        public IFhirSystemDao getSystemDao() {
086                IFhirSystemDao retVal = mySystemDao;
087                if (retVal == null) {
088                        retVal = myAppCtx.getBean(IFhirSystemDao.class);
089                        mySystemDao = retVal;
090                }
091                return retVal;
092        }
093
094        /**
095         * @throws InvalidRequestException If the given resource type is not supported
096         */
097        public IFhirResourceDao getResourceDao(String theResourceName) {
098                IFhirResourceDao<IBaseResource> retVal = getResourceDaoOrNull(theResourceName);
099                if (retVal == null) {
100                        List<String> supportedResourceTypes =
101                                        myResourceNameToResourceDao.keySet().stream().sorted().collect(Collectors.toList());
102                        throw new InvalidRequestException(Msg.code(572)
103                                        + "Unable to process request, this server does not know how to handle resources of type "
104                                        + theResourceName + " - Can handle: " + supportedResourceTypes);
105                }
106                return retVal;
107        }
108
109        @SuppressWarnings("unchecked")
110        public <R extends IBaseResource> IFhirResourceDao<R> getResourceDao(R theResource) {
111                return (IFhirResourceDao<R>) getResourceDao(theResource.getClass());
112        }
113
114        public <R extends IBaseResource> IFhirResourceDao<R> getResourceDao(Class<R> theResourceType) {
115                IFhirResourceDao<R> retVal = getResourceDaoIfExists(theResourceType);
116                Validate.notNull(
117                                retVal, "No DAO exists for resource type %s - Have: %s", theResourceType, myResourceNameToResourceDao);
118                return retVal;
119        }
120
121        /**
122         * Use getResourceDaoOrNull
123         */
124        @Deprecated
125        public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoIfExists(Class<T> theResourceType) {
126                return getResourceDaoOrNull(theResourceType);
127        }
128
129        @Nullable
130        public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoOrNull(Class<T> theResourceType) {
131                String resourceName = myContext.getResourceType(theResourceType);
132                try {
133                        return (IFhirResourceDao<T>) getResourceDao(resourceName);
134                } catch (InvalidRequestException e) {
135                        return null;
136                }
137        }
138
139        /**
140         * Use getResourceDaoOrNull
141         */
142        @Deprecated
143        public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoIfExists(String theResourceType) {
144                return getResourceDaoOrNull(theResourceType);
145        }
146
147        @Nullable
148        public <T extends IBaseResource> IFhirResourceDao<T> getResourceDaoOrNull(String theResourceName) {
149                init();
150                return (IFhirResourceDao<T>) myResourceNameToResourceDao.get(theResourceName);
151        }
152
153        @Override
154        public boolean isResourceTypeSupported(String theResourceType) {
155                if (mySupportedResourceTypes == null) {
156                        return getResourceDaoOrNull(theResourceType) != null;
157                }
158                return mySupportedResourceTypes.contains(theResourceType);
159        }
160
161        private void init() {
162                if (myResourceNameToResourceDao != null && !myResourceNameToResourceDao.isEmpty()) {
163                        return;
164                }
165
166                Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
167
168                initializeMaps(resourceDaos.values());
169        }
170
171        private void initializeMaps(Collection<IFhirResourceDao> theResourceDaos) {
172
173                myResourceNameToResourceDao = new HashMap<>();
174
175                for (IFhirResourceDao nextResourceDao : theResourceDaos) {
176                        Class resourceType = nextResourceDao.getResourceType();
177                        assert resourceType != null;
178                        RuntimeResourceDefinition nextResourceDef = myContext.getResourceDefinition(resourceType);
179                        if (mySupportedResourceTypes == null || mySupportedResourceTypes.contains(nextResourceDef.getName())) {
180                                myResourceNameToResourceDao.put(nextResourceDef.getName(), nextResourceDao);
181                        }
182                }
183        }
184
185        public void register(IFhirResourceDao theResourceDao) {
186                RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceDao.getResourceType());
187                String resourceName = resourceDef.getName();
188                myResourceNameToResourceDao.put(resourceName, theResourceDao);
189        }
190
191        public IFhirResourceDao getDaoOrThrowException(Class<? extends IBaseResource> theClass) {
192                IFhirResourceDao retVal = getResourceDao(theClass);
193                if (retVal == null) {
194                        List<String> supportedResourceNames = myResourceNameToResourceDao.keySet().stream()
195                                        .map(t -> myContext.getResourceType(t))
196                                        .sorted()
197                                        .collect(Collectors.toList());
198                        throw new InvalidRequestException(Msg.code(573)
199                                        + "Unable to process request, this server does not know how to handle resources of type "
200                                        + myContext.getResourceType(theClass) + " - Can handle: " + supportedResourceNames);
201                }
202                return retVal;
203        }
204
205        public void setResourceDaos(Collection<IFhirResourceDao> theResourceDaos) {
206                initializeMaps(theResourceDaos);
207        }
208
209        public IFhirResourceDao getSubscriptionDao() {
210                return getResourceDao(ResourceTypeEnum.SUBSCRIPTION.getCode());
211        }
212
213        public void setSupportedResourceTypes(String... theResourceTypes) {
214                setSupportedResourceTypes(toCollection(theResourceTypes));
215        }
216
217        private List<String> toCollection(String[] theResourceTypes) {
218                List<String> retVal = null;
219                if (theResourceTypes != null && theResourceTypes.length > 0) {
220                        retVal = Arrays.asList(theResourceTypes);
221                }
222                return retVal;
223        }
224
225        public Set<String> getRegisteredDaoTypes() {
226                return Collections.unmodifiableSet(myResourceNameToResourceDao.keySet());
227        }
228}