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         * @deprecated since 7.7.  Use  {@link FhirContext#forVersion(FhirVersionEnum)} instead
139         */
140        @Deprecated(forRemoval = true, since = "7.7")
141        public FhirContext newContext() {
142                return FhirContext.forVersion(this);
143        }
144
145        /**
146         * Creates a new FhirContext for this FHIR version, or returns a previously created one if one exists. This
147         * method uses {@link FhirContext#forCached(FhirVersionEnum)} to return a cached instance.
148         * @deprecated since 7.7.  Use  {@link FhirContext#forCached(FhirVersionEnum)} instead
149         */
150        @Deprecated(forRemoval = true, since = "7.7")
151        public FhirContext newContextCached() {
152                return FhirContext.forCached(this);
153        }
154
155        private interface IVersionProvider {
156                String provideVersion();
157        }
158
159        /**
160         * Given a FHIR model object type, determine which version of FHIR it is for
161         */
162        public static FhirVersionEnum determineVersionForType(Class<?> theFhirType) {
163                switch (theFhirType.getName()) {
164                        case "ca.uhn.fhir.model.api.BaseElement":
165                                return DSTU2;
166                        case "org.hl7.fhir.dstu2.model.Base":
167                                return DSTU2_HL7ORG;
168                        case "org.hl7.fhir.dstu3.model.Base":
169                                return DSTU3;
170                        case "org.hl7.fhir.r4.model.Base":
171                                return R4;
172                        case "org.hl7.fhir.r5.model.Base":
173                                return R5;
174                        case "java.lang.Object":
175                                return null;
176                        default:
177                                return determineVersionForType(theFhirType.getSuperclass());
178                }
179        }
180
181        private static class Version implements IVersionProvider {
182
183                private String myVersion;
184
185                public Version(String theVersion) {
186                        super();
187                        myVersion = theVersion;
188                }
189
190                @Override
191                public String provideVersion() {
192                        return myVersion;
193                }
194        }
195
196        /**
197         * This class attempts to read the FHIR version from the actual model
198         * classes in order to supply an accurate version string even over time
199         */
200        private static class Dstu3Version implements IVersionProvider {
201
202                private String myVersion;
203
204                Dstu3Version() {
205                        try {
206                                Class<?> c = Class.forName("org.hl7.fhir.dstu3.model.Constants");
207                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
208                        } catch (Exception e) {
209                                myVersion = "3.0.2";
210                        }
211                }
212
213                @Override
214                public String provideVersion() {
215                        return myVersion;
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        private static class R4BVersion implements IVersionProvider {
239
240                private String myVersion;
241
242                R4BVersion() {
243                        try {
244                                Class<?> c = Class.forName("org.hl7.fhir.r4b.model.Constants");
245                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
246                        } catch (Exception e) {
247                                myVersion = "4.3.0";
248                        }
249                }
250
251                @Override
252                public String provideVersion() {
253                        return myVersion;
254                }
255        }
256
257        private static class R5Version implements IVersionProvider {
258
259                private String myVersion;
260
261                R5Version() {
262                        try {
263                                Class<?> c = Class.forName("org.hl7.fhir.r5.model.Constants");
264                                myVersion = (String) c.getDeclaredField("VERSION").get(null);
265                        } catch (Exception e) {
266                                myVersion = "5.0.0";
267                        }
268                }
269
270                @Override
271                public String provideVersion() {
272                        return myVersion;
273                }
274        }
275
276        /**
277         * Returns the {@link FhirVersionEnum} which corresponds to a specific version of
278         * FHIR. Partial version strings (e.g. "3.0") are acceptable. This method will
279         * also accept version names such as "DSTU2", "STU3", "R5", etc.
280         *
281         * @return Returns null if no version exists matching the given string
282         */
283        public static FhirVersionEnum forVersionString(String theVersionString) {
284
285                // Trim the point release
286                String versionString = theVersionString;
287                int firstDot = versionString.indexOf('.');
288                if (firstDot > 0) {
289                        int secondDot = versionString.indexOf('.', firstDot + 1);
290                        if (secondDot > 0) {
291                                versionString = versionString.substring(0, secondDot);
292                        }
293                }
294
295                for (FhirVersionEnum next : values()) {
296                        if (next.getFhirVersionString().startsWith(versionString)) {
297                                return next;
298                        }
299                }
300
301                switch (theVersionString) {
302                        case "DSTU2":
303                                return FhirVersionEnum.DSTU2;
304                        case "DSTU3":
305                        case "STU3":
306                                return FhirVersionEnum.DSTU3;
307                        case "R4":
308                                return FhirVersionEnum.R4;
309                        case "R4B":
310                                return FhirVersionEnum.R4B;
311                        case "R5":
312                                return FhirVersionEnum.R5;
313                }
314
315                return null;
316        }
317}