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