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