001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 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(
061                        String theVersionClass,
062                        FhirVersionEnum theEquivalent,
063                        boolean theIsRi,
064                        IVersionProvider theVersionExtractor) {
065                myVersionClass = theVersionClass;
066                myEquivalent = theEquivalent;
067                myFhirVersionString = theVersionExtractor.provideVersion();
068                myIsRi = theIsRi;
069        }
070
071        public String getFhirVersionString() {
072                return myFhirVersionString;
073        }
074
075        public IFhirVersion getVersionImplementation() {
076                if (!isPresentOnClasspath()) {
077                        throw new IllegalStateException(Msg.code(1709) + "Version " + name() + " is not present on classpath");
078                }
079                if (myVersionImplementation == null) {
080                        try {
081                                myVersionImplementation =
082                                                (IFhirVersion) Class.forName(myVersionClass).newInstance();
083                        } catch (Exception e) {
084                                throw new InternalErrorException(Msg.code(1710) + "Failed to instantiate FHIR version " + name(), e);
085                        }
086                }
087                return myVersionImplementation;
088        }
089
090        public boolean isEqualOrNewerThan(FhirVersionEnum theVersion) {
091                return ordinal() >= theVersion.ordinal();
092        }
093
094        public boolean isEquivalentTo(FhirVersionEnum theVersion) {
095                if (this.equals(theVersion)) {
096                        return true;
097                }
098                if (myEquivalent != null) {
099                        return myEquivalent.equals(theVersion);
100                }
101                return false;
102        }
103
104        public boolean isNewerThan(FhirVersionEnum theVersion) {
105                return !isEquivalentTo(theVersion) && ordinal() > theVersion.ordinal();
106        }
107
108        public boolean isOlderThan(FhirVersionEnum theVersion) {
109                return !isEquivalentTo(theVersion) && ordinal() < theVersion.ordinal();
110        }
111
112        /**
113         * Returns true if the given version is present on the classpath
114         */
115        public boolean isPresentOnClasspath() {
116                Boolean retVal = myPresentOnClasspath;
117                if (retVal == null) {
118                        try {
119                                Class.forName(myVersionClass);
120                                retVal = true;
121                        } catch (Exception e) {
122                                retVal = false;
123                        }
124                        myPresentOnClasspath = retVal;
125                }
126                return retVal;
127        }
128
129        /**
130         * Is this version using the HL7.org RI structures?
131         */
132        public boolean isRi() {
133                return myIsRi;
134        }
135
136        /**
137         * Creates a new FhirContext for this FHIR version
138         */
139        public FhirContext newContext() {
140                return new FhirContext(this);
141        }
142
143        /**
144         * Creates a new FhirContext for this FHIR version, or returns a previously created one if one exists. This
145         * method uses {@link FhirContext#forCached(FhirVersionEnum)} to return a cached instance.
146         */
147        public FhirContext newContextCached() {
148                return FhirContext.forCached(this);
149        }
150
151        private interface IVersionProvider {
152                String provideVersion();
153        }
154
155        /**
156         * Given a FHIR model object type, determine which version of FHIR it is for
157         */
158        public static FhirVersionEnum determineVersionForType(Class<?> theFhirType) {
159                switch (theFhirType.getName()) {
160                        case "ca.uhn.fhir.model.api.BaseElement":
161                                return DSTU2;
162                        case "org.hl7.fhir.dstu2.model.Base":
163                                return DSTU2_HL7ORG;
164                        case "org.hl7.fhir.dstu3.model.Base":
165                                return DSTU3;
166                        case "org.hl7.fhir.r4.model.Base":
167                                return R4;
168                        case "org.hl7.fhir.r5.model.Base":
169                                return R5;
170                        case "java.lang.Object":
171                                return null;
172                        default:
173                                return determineVersionForType(theFhirType.getSuperclass());
174                }
175        }
176
177        private static class Version implements IVersionProvider {
178
179                private String myVersion;
180
181                public Version(String theVersion) {
182                        super();
183                        myVersion = theVersion;
184                }
185
186                @Override
187                public String provideVersion() {
188                        return myVersion;
189                }
190        }
191
192        /**
193         * This class attempts to read the FHIR version from the actual model
194         * classes in order to supply an accurate version string even over time
195         */
196        private static class Dstu3Version implements IVersionProvider {
197
198                private String myVersion;
199
200                Dstu3Version() {
201                        try {
202                                Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
203                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
204                        } catch (Exception e) {
205                                myVersion = "3.0.2";
206                        }
207                }
208
209                @Override
210                public String provideVersion() {
211                        return myVersion;
212                }
213        }
214
215        private static class R4Version implements IVersionProvider {
216
217                private String myVersion;
218
219                R4Version() {
220                        try {
221                                Class<?> c = Class.forName("org.hl7.fhir.r4.model.Constants");
222                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
223                        } catch (Exception e) {
224                                myVersion = "4.0.2";
225                        }
226                }
227
228                @Override
229                public String provideVersion() {
230                        return myVersion;
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        private static class R5Version implements IVersionProvider {
254
255                private String myVersion;
256
257                R5Version() {
258                        try {
259                                Class<?> c = Class.forName("org.hl7.fhir.r5.model.Constants");
260                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
261                        } catch (Exception e) {
262                                myVersion = "5.0.0";
263                        }
264                }
265
266                @Override
267                public String provideVersion() {
268                        return myVersion;
269                }
270        }
271
272        /**
273         * Returns the {@link FhirVersionEnum} which corresponds to a specific version of
274         * FHIR. Partial version strings (e.g. "3.0") are acceptable. This method will
275         * also accept version names such as "DSTU2", "STU3", "R5", etc.
276         *
277         * @return Returns null if no version exists matching the given string
278         */
279        public static FhirVersionEnum forVersionString(String theVersionString) {
280
281                // Trim the point release
282                String versionString = theVersionString;
283                int firstDot = versionString.indexOf('.');
284                if (firstDot > 0) {
285                        int secondDot = versionString.indexOf('.', firstDot + 1);
286                        if (secondDot > 0) {
287                                versionString = versionString.substring(0, secondDot);
288                        }
289                }
290
291                for (FhirVersionEnum next : values()) {
292                        if (next.getFhirVersionString().startsWith(versionString)) {
293                                return next;
294                        }
295                }
296
297                switch (theVersionString) {
298                        case "DSTU2":
299                                return FhirVersionEnum.DSTU2;
300                        case "DSTU3":
301                        case "STU3":
302                                return FhirVersionEnum.DSTU3;
303                        case "R4":
304                                return FhirVersionEnum.R4;
305                        case "R4B":
306                                return FhirVersionEnum.R4B;
307                        case "R5":
308                                return FhirVersionEnum.R5;
309                }
310
311                return null;
312        }
313}