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}