001package ca.uhn.fhir.rest.api;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2018 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        public String getFormatContentType() {
110                return myFormatContentType;
111        }
112
113        /**
114         * Will return application/xml+fhir style
115         */
116        public String getResourceContentType() {
117                return myResourceContentTypeLegacy;
118        }
119
120        /**
121         * Will return application/fhir+xml style
122         */
123        public String getResourceContentTypeNonLegacy() {
124                return myResourceContentTypeNonLegacy;
125        }
126
127        public abstract IParser newParser(FhirContext theContext);
128
129        public static EncodingEnum detectEncoding(String theBody) {
130                EncodingEnum retVal = detectEncodingNoDefault(theBody);
131                retVal = ObjectUtils.defaultIfNull(retVal, EncodingEnum.XML);
132                return retVal;
133        }
134
135        public static EncodingEnum detectEncodingNoDefault(String theBody) {
136                EncodingEnum retVal = null;
137                for (int i = 0; i < theBody.length() && retVal == null; i++) {
138                        switch (theBody.charAt(i)) {
139                                case '<':
140                                        retVal = EncodingEnum.XML;
141                                        break;
142                                case '{':
143                                        retVal = EncodingEnum.JSON;
144                                        break;
145                        }
146                }
147                return retVal;
148        }
149
150        /**
151         * Returns the encoding for a given content type, or <code>null</code> if no encoding
152         * is found.
153         * <p>
154         * <b>This method is lenient!</b> Things like "application/xml" will return {@link EncodingEnum#XML}
155         * even if the "+fhir" part is missing from the expected content type.
156         * </p>
157         */
158        public static EncodingEnum forContentType(String theContentType) {
159                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
160                if (contentTypeSplitted == null) {
161                        return null;
162                } else {
163                        return ourContentTypeToEncoding.get(contentTypeSplitted );
164                }
165        }
166
167
168        /**
169         * Returns the encoding for a given content type, or <code>null</code> if no encoding
170         * is found.
171         * <p>
172         * <b>This method is NOT lenient!</b> Things like "application/xml" will return <code>null</code>
173         * </p>
174         *
175         * @see #forContentType(String)
176         */
177        public static EncodingEnum forContentTypeStrict(String theContentType) {
178                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
179                if (contentTypeSplitted == null) {
180                        return null;
181                } else {
182                        return ourContentTypeToEncodingStrict.get(contentTypeSplitted);
183                }
184        }
185
186        private static String getTypeWithoutCharset(String theContentType) {
187                if (theContentType == null) {
188                        return null;
189                } else {
190                        String[] contentTypeSplitted = theContentType.split(";");
191                        return contentTypeSplitted[0];
192                }
193        }
194
195        /**
196         * Is the given type a FHIR legacy (pre-DSTU3) content type?
197         */
198        public static boolean isLegacy(String theContentType) {
199                String contentTypeSplitted = getTypeWithoutCharset(theContentType);
200                if (contentTypeSplitted == null) {
201                        return false;
202                } else {
203                        return ourContentTypeToEncodingLegacy.containsKey(contentTypeSplitted);
204                }
205        }
206
207
208}