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