001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2023 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.model.api.IFhirVersion;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026
027public enum FhirVersionEnum {
028
029        /*
030         * ***********************
031         * Don't auto-sort this type!!!
032         *
033         * Or more accurately, entries should be sorted from OLDEST FHIR release
034         * to NEWEST FHIR release instead of alphabetically
035         * ***********************
036         */
037
038        DSTU2("ca.uhn.fhir.model.dstu2.FhirDstu2", null, false, new Version("1.0.2")),
039
040        DSTU2_HL7ORG("org.hl7.fhir.dstu2.hapi.ctx.FhirDstu2Hl7Org", DSTU2, true, new Version("1.0.2")),
041
042        DSTU2_1("org.hl7.fhir.dstu2016may.hapi.ctx.FhirDstu2_1", null, true, new Version("1.4.0")),
043
044        DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, new Dstu3Version()),
045
046        R4("org.hl7.fhir.r4.hapi.ctx.FhirR4", null, true, new R4Version()),
047
048        R4B("org.hl7.fhir.r4b.hapi.ctx.FhirR4B", null, true, new R4BVersion()),
049
050        R5("org.hl7.fhir.r5.hapi.ctx.FhirR5", null, true, new R5Version());
051
052        // If you add new constants, add to the various methods below too!
053
054        private final FhirVersionEnum myEquivalent;
055        private final boolean myIsRi;
056        private final String myVersionClass;
057        private volatile Boolean myPresentOnClasspath;
058        private volatile IFhirVersion myVersionImplementation;
059        private String myFhirVersionString;
060
061        FhirVersionEnum(String theVersionClass, FhirVersionEnum theEquivalent, boolean theIsRi, IVersionProvider theVersionExtractor) {
062                myVersionClass = theVersionClass;
063                myEquivalent = theEquivalent;
064                myFhirVersionString = theVersionExtractor.provideVersion();
065                myIsRi = theIsRi;
066        }
067
068        public String getFhirVersionString() {
069                return myFhirVersionString;
070        }
071
072        public IFhirVersion getVersionImplementation() {
073                if (!isPresentOnClasspath()) {
074                        throw new IllegalStateException(Msg.code(1709) + "Version " + name() + " is not present on classpath");
075                }
076                if (myVersionImplementation == null) {
077                        try {
078                                myVersionImplementation = (IFhirVersion) Class.forName(myVersionClass).newInstance();
079                        } catch (Exception e) {
080                                throw new InternalErrorException(Msg.code(1710) + "Failed to instantiate FHIR version " + name(), e);
081                        }
082                }
083                return myVersionImplementation;
084        }
085
086        public boolean isEqualOrNewerThan(FhirVersionEnum theVersion) {
087                return ordinal() >= theVersion.ordinal();
088        }
089
090        public boolean isEquivalentTo(FhirVersionEnum theVersion) {
091                if (this.equals(theVersion)) {
092                        return true;
093                }
094                if (myEquivalent != null) {
095                        return myEquivalent.equals(theVersion);
096                }
097                return false;
098        }
099
100        public boolean isNewerThan(FhirVersionEnum theVersion) {
101                return !isEquivalentTo(theVersion) && ordinal() > theVersion.ordinal();
102        }
103
104        public boolean isOlderThan(FhirVersionEnum theVersion) {
105                return !isEquivalentTo(theVersion) && ordinal() < theVersion.ordinal();
106        }
107
108        /**
109         * Returns true if the given version is present on the classpath
110         */
111        public boolean isPresentOnClasspath() {
112                Boolean retVal = myPresentOnClasspath;
113                if (retVal == null) {
114                        try {
115                                Class.forName(myVersionClass);
116                                retVal = true;
117                        } catch (Exception e) {
118                                retVal = false;
119                        }
120                        myPresentOnClasspath = retVal;
121                }
122                return retVal;
123        }
124
125        /**
126         * Is this version using the HL7.org RI structures?
127         */
128        public boolean isRi() {
129                return myIsRi;
130        }
131
132        public FhirContext newContext() {
133                switch (this) {
134                        case DSTU2:
135                                return FhirContext.forDstu2();
136                        case DSTU2_HL7ORG:
137                                return FhirContext.forDstu2Hl7Org();
138                        case DSTU2_1:
139                                return FhirContext.forDstu2_1();
140                        case DSTU3:
141                                return FhirContext.forDstu3();
142                        case R4:
143                                return FhirContext.forR4();
144                        case R4B:
145                                return FhirContext.forR4B();
146                        case R5:
147                                return FhirContext.forR5();
148                }
149                throw new IllegalStateException(Msg.code(1711) + "Unknown version: " + this); // should not happen
150        }
151
152        private interface IVersionProvider {
153                String provideVersion();
154        }
155
156        /**
157         * Given a FHIR model object type, determine which version of FHIR it is for
158         */
159        public static FhirVersionEnum determineVersionForType(Class<?> theFhirType) {
160                switch (theFhirType.getName()) {
161                        case "ca.uhn.fhir.model.api.BaseElement":
162                                return DSTU2;
163                        case "org.hl7.fhir.dstu2.model.Base":
164                                return DSTU2_HL7ORG;
165                        case "org.hl7.fhir.dstu3.model.Base":
166                                return DSTU3;
167                        case "org.hl7.fhir.r4.model.Base":
168                                return R4;
169                        case "org.hl7.fhir.r5.model.Base":
170                                return R5;
171                        case "java.lang.Object":
172                                return null;
173                        default:
174                                return determineVersionForType(theFhirType.getSuperclass());
175                }
176
177        }
178
179        private static class Version implements IVersionProvider {
180
181                private String myVersion;
182
183                public Version(String theVersion) {
184                        super();
185                        myVersion = theVersion;
186                }
187
188                @Override
189                public String provideVersion() {
190                        return myVersion;
191                }
192
193        }
194
195        /**
196         * This class attempts to read the FHIR version from the actual model
197         * classes in order to supply an accurate version string even over time
198         */
199        private static class Dstu3Version implements IVersionProvider {
200
201                private String myVersion;
202
203                Dstu3Version() {
204                        try {
205                                Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
206                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
207                        } catch (Exception e) {
208                                myVersion = "3.0.2";
209                        }
210                }
211
212                @Override
213                public String provideVersion() {
214                        return myVersion;
215                }
216
217        }
218
219        private static class R4Version implements IVersionProvider {
220
221                private String myVersion;
222
223                R4Version() {
224                        try {
225                                Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
226                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
227                        } catch (Exception e) {
228                                myVersion = "4.0.2";
229                        }
230                }
231
232                @Override
233                public String provideVersion() {
234                        return myVersion;
235                }
236
237        }
238
239        private static class R4BVersion implements IVersionProvider {
240
241                private String myVersion;
242
243                R4BVersion() {
244                        try {
245                                Class<?> c = Class.forName("org.hl7.fhir.r4b.model.Constants");
246                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
247                        } catch (Exception e) {
248                                myVersion = "4.3.0";
249                        }
250                }
251
252                @Override
253                public String provideVersion() {
254                        return myVersion;
255                }
256
257        }
258
259        private static class R5Version implements IVersionProvider {
260
261                private String myVersion;
262
263                R5Version() {
264                        try {
265                                Class<?> c = Class.forName("org.hl7.fhir.r5.model.Constants");
266                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
267                        } catch (Exception e) {
268                                myVersion = "5.0.0";
269                        }
270                }
271
272                @Override
273                public String provideVersion() {
274                        return myVersion;
275                }
276
277        }
278
279        /**
280         * Returns the {@link FhirVersionEnum} which corresponds to a specific version of
281         * FHIR. Partial version strings (e.g. "3.0") are acceptable. This method will
282         * also accept version names such as "DSTU2", "STU3", "R5", etc.
283         *
284         * @return Returns null if no version exists matching the given string
285         */
286        public static FhirVersionEnum forVersionString(String theVersionString) {
287
288                // Trim the point release
289                String versionString = theVersionString;
290                int firstDot = versionString.indexOf('.');
291                if (firstDot > 0) {
292                        int secondDot = versionString.indexOf('.', firstDot + 1);
293                        if (secondDot > 0) {
294                                versionString = versionString.substring(0, secondDot);
295                        }
296                }
297
298                for (FhirVersionEnum next : values()) {
299                        if (next.getFhirVersionString().startsWith(versionString)) {
300                                return next;
301                        }
302                }
303
304                switch (theVersionString) {
305                        case "DSTU2":
306                                return FhirVersionEnum.DSTU2;
307                        case "DSTU3":
308                        case "STU3":
309                                return FhirVersionEnum.DSTU3;
310                        case "R4":
311                                return FhirVersionEnum.R4;
312                        case "R4B":
313                                return FhirVersionEnum.R4B;
314                        case "R5":
315                                return FhirVersionEnum.R5;
316                }
317
318                return null;
319        }
320
321}