001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 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.model.api.annotation.ResourceDef;
024import ca.uhn.fhir.util.UrlUtil;
025import org.hl7.fhir.instance.model.api.IAnyResource;
026import org.hl7.fhir.instance.model.api.IBase;
027import org.hl7.fhir.instance.model.api.IBaseResource;
028import org.hl7.fhir.instance.model.api.IDomainResource;
029
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.Comparator;
033import java.util.HashMap;
034import java.util.LinkedHashMap;
035import java.util.List;
036import java.util.Map;
037
038public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> {
039
040        private Class<? extends IBaseResource> myBaseType;
041        private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams;
042        private FhirContext myContext;
043        private String myId;
044        private Map<String, RuntimeSearchParam> myNameToSearchParam = new LinkedHashMap<String, RuntimeSearchParam>();
045        private IBaseResource myProfileDef;
046        private String myResourceProfile;
047        private List<RuntimeSearchParam> mySearchParams;
048        private final FhirVersionEnum myStructureVersion;
049        private volatile RuntimeResourceDefinition myBaseDefinition;
050
051
052
053        public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
054                super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions);
055                myContext = theContext;
056                myResourceProfile = theResourceAnnotation.profile();
057                myId = theResourceAnnotation.id();
058
059                IBaseResource instance;
060                try {
061                        instance = theClass.getConstructor().newInstance();
062                } catch (Exception e) {
063                        throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "nonInstantiableType", theClass.getName(), e.toString()), e);
064                }
065                myStructureVersion = instance.getStructureFhirVersionEnum();
066                if (myStructureVersion != theContext.getVersion().getVersion()) {
067                        throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "typeWrongVersion", theContext.getVersion().getVersion(), theClass.getName(), myStructureVersion));
068                }
069
070        }
071
072
073        public void addSearchParam(RuntimeSearchParam theParam) {
074                myNameToSearchParam.put(theParam.getName(), theParam);
075        }
076
077        /**
078         * If this definition refers to a class which extends another resource definition type, this
079         * method will return the definition of the topmost resource. For example, if this definition
080         * refers to MyPatient2, which extends MyPatient, which in turn extends Patient, this method
081         * will return the resource definition for Patient.
082         * <p>
083         * If the definition has no parent, returns <code>this</code>
084         * </p>
085         */
086        public RuntimeResourceDefinition getBaseDefinition() {
087                validateSealed();
088                if (myBaseDefinition == null) {
089                        myBaseDefinition = myContext.getResourceDefinition(myBaseType);
090                }
091                return myBaseDefinition;
092        }
093
094        @Override
095        public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
096                return ChildTypeEnum.RESOURCE;
097        }
098
099        public String getId() {
100                return myId;
101        }
102
103        /**
104         * Express {@link #getImplementingClass()} as theClass (to prevent casting warnings)
105         */
106        @SuppressWarnings("unchecked")
107        public <T> Class<T> getImplementingClass(Class<T> theClass) {
108                if (!theClass.isAssignableFrom(getImplementingClass())) {
109                        throw new ConfigurationException("Unable to convert " + getImplementingClass() + " to " + theClass);
110                }
111                return (Class<T>) getImplementingClass();
112        }
113
114        @Deprecated
115        public String getResourceProfile() {
116                return myResourceProfile;
117        }
118
119        public String getResourceProfile(String theServerBase) {
120                validateSealed();
121                String profile;
122                if (!myResourceProfile.isEmpty()) {
123                        profile = myResourceProfile;
124                } else if (!myId.isEmpty()) {
125                        profile = myId;
126                } else {
127                        return "";
128                }
129
130                if (!UrlUtil.isValid(profile)) {
131                        String resourceName = "/StructureDefinition/";
132                        String profileWithUrl = theServerBase + resourceName + profile;
133                        if (UrlUtil.isValid(profileWithUrl)) {
134                                return profileWithUrl;
135                        }
136                }
137                return profile;
138        }
139
140        public RuntimeSearchParam getSearchParam(String theName) {
141                validateSealed();
142                return myNameToSearchParam.get(theName);
143        }
144
145        public List<RuntimeSearchParam> getSearchParams() {
146                validateSealed();
147                return mySearchParams;
148        }
149
150        /**
151         * Will not return null
152         */
153        public List<RuntimeSearchParam> getSearchParamsForCompartmentName(String theCompartmentName) {
154                validateSealed();
155                List<RuntimeSearchParam> retVal = myCompartmentNameToSearchParams.get(theCompartmentName);
156                if (retVal == null) {
157                        return Collections.emptyList();
158                }
159                return retVal;
160        }
161
162        public FhirVersionEnum getStructureVersion() {
163                return myStructureVersion;
164        }
165
166        public boolean isBundle() {
167                return "Bundle".equals(getName());
168        }
169
170        @SuppressWarnings("unchecked")
171        @Override
172        public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
173                super.sealAndInitialize(theContext, theClassToElementDefinitions);
174
175                myNameToSearchParam = Collections.unmodifiableMap(myNameToSearchParam);
176
177                ArrayList<RuntimeSearchParam> searchParams = new ArrayList<RuntimeSearchParam>(myNameToSearchParam.values());
178                Collections.sort(searchParams, new Comparator<RuntimeSearchParam>() {
179                        @Override
180                        public int compare(RuntimeSearchParam theArg0, RuntimeSearchParam theArg1) {
181                                return theArg0.getName().compareTo(theArg1.getName());
182                        }
183                });
184                mySearchParams = Collections.unmodifiableList(searchParams);
185
186                Map<String, List<RuntimeSearchParam>> compartmentNameToSearchParams = new HashMap<>();
187                for (RuntimeSearchParam next : searchParams) {
188                        if (next.getProvidesMembershipInCompartments() != null) {
189                                for (String nextCompartment : next.getProvidesMembershipInCompartments()) {
190
191                                        if (nextCompartment.startsWith("Base FHIR compartment definition for ")) {
192                                                nextCompartment = nextCompartment.substring("Base FHIR compartment definition for ".length());
193                                        }
194
195                                        if (!compartmentNameToSearchParams.containsKey(nextCompartment)) {
196                                                compartmentNameToSearchParams.put(nextCompartment, new ArrayList<>());
197                                        }
198                                        List<RuntimeSearchParam> searchParamsForCompartment = compartmentNameToSearchParams.get(nextCompartment);
199                                        searchParamsForCompartment.add(next);
200
201                                        /*
202                                         * If one search parameter marks an SP as making a resource
203                                         * a part of a compartment, let's also denote all other
204                                         * SPs with the same path the same way. This behaviour is
205                                         * used by AuthorizationInterceptor
206                                         */
207                                        String nextPath = massagePathForCompartmentSimilarity(next.getPath());
208                                        for (RuntimeSearchParam nextAlternate : searchParams) {
209                                                String nextAlternatePath = massagePathForCompartmentSimilarity(nextAlternate.getPath());
210                                                if (nextAlternatePath.equals(nextPath)) {
211                                                        if (!nextAlternate.getName().equals(next.getName())) {
212                                                                searchParamsForCompartment.add(nextAlternate);
213                                                        }
214                                                }
215                                        }
216                                }
217                        }
218                }
219                myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
220
221                Class<?> target = getImplementingClass();
222                myBaseType = (Class<? extends IBaseResource>) target;
223                do {
224                        target = target.getSuperclass();
225                        if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) {
226                                myBaseType = (Class<? extends IBaseResource>) target;
227                        }
228                } while (target.equals(Object.class) == false);
229                
230                /*
231                 * See #504:
232                 * Bundle types may not have extensions
233                 */
234                if (hasExtensions()) {
235                        if (IAnyResource.class.isAssignableFrom(getImplementingClass())) {
236                                if (!IDomainResource.class.isAssignableFrom(getImplementingClass())) {
237                                        throw new ConfigurationException("Class \"" + getImplementingClass() + "\" is invalid. This resource type is not a DomainResource, it must not have extensions");
238                                }
239                        }
240                }
241
242        }
243
244        private String massagePathForCompartmentSimilarity(String thePath) {
245                String path = thePath;
246                if (path.matches(".*\\.where\\(resolve\\(\\) is [a-zA-Z]+\\)")) {
247                        path = path.substring(0, path.indexOf(".where"));
248                }
249                return path;
250        }
251
252        @Deprecated
253        public synchronized IBaseResource toProfile() {
254                validateSealed();
255                if (myProfileDef != null) {
256                        return myProfileDef;
257                }
258
259                IBaseResource retVal = myContext.getVersion().generateProfile(this, null);
260                myProfileDef = retVal;
261
262                return retVal;
263        }
264
265        public synchronized IBaseResource toProfile(String theServerBase) {
266                validateSealed();
267                if (myProfileDef != null) {
268                        return myProfileDef;
269                }
270
271                IBaseResource retVal = myContext.getVersion().generateProfile(this, theServerBase);
272                myProfileDef = retVal;
273
274                return retVal;
275        }
276
277}