001package ca.uhn.fhir.rest.api;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
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.context.FhirContext;
024import ca.uhn.fhir.parser.IParser;
025import org.apache.commons.lang3.ObjectUtils;
026
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Map;
030
031public enum EncodingEnum {
032
033        JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON_NEW, Constants.FORMAT_JSON) {
034                @Override
035                public IParser newParser(FhirContext theContext) {
036                        return theContext.newJsonParser();
037                }
038        },
039
040        XML(Constants.CT_FHIR_XML, Constants.CT_FHIR_XML_NEW, Constants.FORMAT_XML) {
041                @Override
042                public IParser newParser(FhirContext theContext) {
043                        return theContext.newXmlParser();
044                }
045        },
046
047        RDF(Constants.CT_RDF_TURTLE, Constants.CT_RDF_TURTLE, Constants.FORMAT_TURTLE) {
048                @Override
049                public IParser newParser(FhirContext theContext) {
050                        return theContext.newRDFParser();
051                }
052        },
053
054        ;
055
056        /**
057         * "json"
058         */
059        public static final String JSON_PLAIN_STRING = "json";
060
061        /**
062         * "xml"
063         */
064        public static final String XML_PLAIN_STRING = "xml";
065
066        private static Map<String, EncodingEnum> ourContentTypeToEncoding;
067        private static Map<String, EncodingEnum> ourContentTypeToEncodingLegacy;
068        private static Map<String, EncodingEnum> ourContentTypeToEncodingStrict;
069
070        static {
071                ourContentTypeToEncoding = new HashMap<>();
072                ourContentTypeToEncodingLegacy = new HashMap<>();
073
074                for (EncodingEnum next : values()) {
075                        ourContentTypeToEncoding.put(next.myResourceContentTypeNonLegacy, next);
076                        ourContentTypeToEncoding.put(next.myResourceContentTypeLegacy, next);
077                        ourContentTypeToEncodingLegacy.put(next.myResourceContentTypeLegacy, next);
078
079                        /*
080                         * See #346
081                         */
082                        ourContentTypeToEncoding.put(next.myResourceContentTypeNonLegacy.replace('+', ' '), next);
083                        ourContentTypeToEncoding.put(next.myResourceContentTypeLegacy.replace('+', ' '), next);
084                        ourContentTypeToEncodingLegacy.put(next.myResourceContentTypeLegacy.replace('+', ' '), next);
085
086                }
087
088                // Add before we add the lenient ones
089                ourContentTypeToEncodingStrict = Collections.unmodifiableMap(new HashMap<>(ourContentTypeToEncoding));
090
091                /*
092                 * These are wrong, but we add them just to be tolerant of other
093                 * people's mistakes
094                 */
095                ourContentTypeToEncoding.put("application/json", JSON);
096                ourContentTypeToEncoding.put("application/xml", XML);
097                ourContentTypeToEncoding.put("text/json", JSON);
098                ourContentTypeToEncoding.put("text/xml", XML);
099
100                /*
101                 * Plain values, used for parameter values
102                 */
103                ourContentTypeToEncoding.put(JSON_PLAIN_STRING, JSON);
104                ourContentTypeToEncoding.put(XML_PLAIN_STRING, XML);
105
106                ourContentTypeToEncodingLegacy = Collections.unmodifiableMap(ourContentTypeToEncodingLegacy);
107
108        }
109
110        private String myFormatContentType;
111        private String myResourceContentTypeLegacy;
112        private String myResourceContentTypeNonLegacy;
113
114        EncodingEnum(String theResourceContentTypeLegacy, String theResourceContentType, String theFormatContentType) {
115                myResourceContentTypeLegacy = theResourceContentTypeLegacy;
116                myResourceContentTypeNonLegacy = theResourceContentType;
117                myFormatContentType = theFormatContentType;
118        }
119
120        /**
121         * Returns <code>xml</code> or <code>json</code> as used on the <code>_format</code> search parameter
122         */
123        public String getFormatContentType() {
124                return myFormatContentType;
125        }
126
127        /**
128         * Will return application/xml+fhir style
129         */
130        public String getResourceContentType() {
131                return myResourceContentTypeLegacy;
132        }
133
134        /**
135         * Will return application/fhir+xml style
136         */
137        public String getResourceContentTypeNonLegacy() {
138                return myResourceContentTypeNonLegacy;
139        }
140
141        public abstract IParser newParser(final FhirContext theContext);
142
143        public static EncodingEnum detectEncoding(final String theBody) {
144                EncodingEnum retVal = detectEncodingNoDefault(theBody);
145                retVal = ObjectUtils.defaultIfNull(retVal, EncodingEnum.XML);
146                return retVal;
147        }
148
149        public static EncodingEnum detectEncodingNoDefault(String theBody) {
150                EncodingEnum retVal = null;
151                for (int i = 0; i < theBody.length() && retVal == null; i++) {
152                        switch (theBody.charAt(i)) {
153                                case '<':
154                                        retVal = EncodingEnum.XML;
155                                        break;
156                                case '{':
157                                        retVal = EncodingEnum.JSON;
158                                        break;
159                        }
160                }
161                return retVal;
162        }
163
164        /**
165         * Returns the encoding for a given content type, or <code>null</code> if no encoding
166         * is found.
167         * <p>
168         * <b>This method is lenient!</b> Things like "application/xml" will return {@link EncodingEnum#XML}
169         * even if the "+fhir" part is missing from the expected content type.
170         * </p>
171         */
172        public static EncodingEnum forContentType(final String theContentType) {
173                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
174                if (contentTypeSplitted == null) {
175                        return null;
176                } else {
177                        return ourContentTypeToEncoding.get(contentTypeSplitted );
178                }
179        }
180
181
182        /**
183         * Returns the encoding for a given content type, or <code>null</code> if no encoding
184         * is found.
185         * <p>
186         * <b>This method is NOT lenient!</b> Things like "application/xml" will return <code>null</code>
187         * </p>
188         *
189         * @see #forContentType(String)
190         */
191        public static EncodingEnum forContentTypeStrict(final String theContentType) {
192                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
193                if (contentTypeSplitted == null) {
194                        return null;
195                } else {
196                        return ourContentTypeToEncodingStrict.get(contentTypeSplitted);
197                }
198        }
199
200        private static String getTypeWithoutCharset(final String theContentType) {
201                if (theContentType == null) {
202                        return null;
203                } else {
204                        String[] contentTypeSplitted = theContentType.split(";");
205                        return contentTypeSplitted[0];
206                }
207        }
208
209        /**
210         * Is the given type a FHIR legacy (pre-DSTU3) content type?
211         */
212        public static boolean isLegacy(final String theContentType) {
213                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
214                if (contentTypeSplitted == null) {
215                        return false;
216                } else {
217                        return ourContentTypeToEncodingLegacy.containsKey(contentTypeSplitted);
218                }
219        }
220
221
222}