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        /**
048         * "json"
049         */
050        public static final String JSON_PLAIN_STRING = "json";
051        /**
052         * "xml"
053         */
054        public static final String XML_PLAIN_STRING = "xml";
055        private static Map<String, EncodingEnum> ourContentTypeToEncoding;
056        private static Map<String, EncodingEnum> ourContentTypeToEncodingLegacy;
057        private static Map<String, EncodingEnum> ourContentTypeToEncodingStrict;
058
059        static {
060                ourContentTypeToEncoding = new HashMap<>();
061                ourContentTypeToEncodingLegacy = new HashMap<>();
062
063                for (EncodingEnum next : values()) {
064                        ourContentTypeToEncoding.put(next.myResourceContentTypeNonLegacy, next);
065                        ourContentTypeToEncoding.put(next.myResourceContentTypeLegacy, next);
066                        ourContentTypeToEncodingLegacy.put(next.myResourceContentTypeLegacy, next);
067
068                        /*
069                         * See #346
070                         */
071                        ourContentTypeToEncoding.put(next.myResourceContentTypeNonLegacy.replace('+', ' '), next);
072                        ourContentTypeToEncoding.put(next.myResourceContentTypeLegacy.replace('+', ' '), next);
073                        ourContentTypeToEncodingLegacy.put(next.myResourceContentTypeLegacy.replace('+', ' '), next);
074
075                }
076
077                // Add before we add the lenient ones
078                ourContentTypeToEncodingStrict = Collections.unmodifiableMap(new HashMap<>(ourContentTypeToEncoding));
079
080                /*
081                 * These are wrong, but we add them just to be tolerant of other
082                 * people's mistakes
083                 */
084                ourContentTypeToEncoding.put("application/json", JSON);
085                ourContentTypeToEncoding.put("application/xml", XML);
086                ourContentTypeToEncoding.put("text/json", JSON);
087                ourContentTypeToEncoding.put("text/xml", XML);
088
089                /*
090                 * Plain values, used for parameter values
091                 */
092                ourContentTypeToEncoding.put(JSON_PLAIN_STRING, JSON);
093                ourContentTypeToEncoding.put(XML_PLAIN_STRING, XML);
094
095                ourContentTypeToEncodingLegacy = Collections.unmodifiableMap(ourContentTypeToEncodingLegacy);
096
097        }
098
099        private String myFormatContentType;
100        private String myResourceContentTypeLegacy;
101        private String myResourceContentTypeNonLegacy;
102
103        EncodingEnum(String theResourceContentTypeLegacy, String theResourceContentType, String theFormatContentType) {
104                myResourceContentTypeLegacy = theResourceContentTypeLegacy;
105                myResourceContentTypeNonLegacy = theResourceContentType;
106                myFormatContentType = theFormatContentType;
107        }
108
109        /**
110         * Returns <code>xml</code> or <code>json</code> as used on the <code>_format</code> search parameter
111         */
112        public String getFormatContentType() {
113                return myFormatContentType;
114        }
115
116        /**
117         * Will return application/xml+fhir style
118         */
119        public String getResourceContentType() {
120                return myResourceContentTypeLegacy;
121        }
122
123        /**
124         * Will return application/fhir+xml style
125         */
126        public String getResourceContentTypeNonLegacy() {
127                return myResourceContentTypeNonLegacy;
128        }
129
130        public abstract IParser newParser(FhirContext theContext);
131
132        public static EncodingEnum detectEncoding(String theBody) {
133                EncodingEnum retVal = detectEncodingNoDefault(theBody);
134                retVal = ObjectUtils.defaultIfNull(retVal, EncodingEnum.XML);
135                return retVal;
136        }
137
138        public static EncodingEnum detectEncodingNoDefault(String theBody) {
139                EncodingEnum retVal = null;
140                for (int i = 0; i < theBody.length() && retVal == null; i++) {
141                        switch (theBody.charAt(i)) {
142                                case '<':
143                                        retVal = EncodingEnum.XML;
144                                        break;
145                                case '{':
146                                        retVal = EncodingEnum.JSON;
147                                        break;
148                        }
149                }
150                return retVal;
151        }
152
153        /**
154         * Returns the encoding for a given content type, or <code>null</code> if no encoding
155         * is found.
156         * <p>
157         * <b>This method is lenient!</b> Things like "application/xml" will return {@link EncodingEnum#XML}
158         * even if the "+fhir" part is missing from the expected content type.
159         * </p>
160         */
161        public static EncodingEnum forContentType(String theContentType) {
162                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
163                if (contentTypeSplitted == null) {
164                        return null;
165                } else {
166                        return ourContentTypeToEncoding.get(contentTypeSplitted );
167                }
168        }
169
170
171        /**
172         * Returns the encoding for a given content type, or <code>null</code> if no encoding
173         * is found.
174         * <p>
175         * <b>This method is NOT lenient!</b> Things like "application/xml" will return <code>null</code>
176         * </p>
177         *
178         * @see #forContentType(String)
179         */
180        public static EncodingEnum forContentTypeStrict(String theContentType) {
181                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
182                if (contentTypeSplitted == null) {
183                        return null;
184                } else {
185                        return ourContentTypeToEncodingStrict.get(contentTypeSplitted);
186                }
187        }
188
189        private static String getTypeWithoutCharset(String theContentType) {
190                if (theContentType == null) {
191                        return null;
192                } else {
193                        String[] contentTypeSplitted = theContentType.split(";");
194                        return contentTypeSplitted[0];
195                }
196        }
197
198        /**
199         * Is the given type a FHIR legacy (pre-DSTU3) content type?
200         */
201        public static boolean isLegacy(String theContentType) {
202                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
203                if (contentTypeSplitted == null) {
204                        return false;
205                } else {
206                        return ourContentTypeToEncodingLegacy.containsKey(contentTypeSplitted);
207                }
208        }
209
210
211}