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