001package org.hl7.fhir.r4.utils.client; 002 003 004 005 006 007/* 008 Copyright (c) 2011+, HL7, Inc. 009 All rights reserved. 010 011 Redistribution and use in source and binary forms, with or without modification, 012 are permitted provided that the following conditions are met: 013 014 * Redistributions of source code must retain the above copyright notice, this 015 list of conditions and the following disclaimer. 016 * Redistributions in binary form must reproduce the above copyright notice, 017 this list of conditions and the following disclaimer in the documentation 018 and/or other materials provided with the distribution. 019 * Neither the name of HL7 nor the names of its contributors may be used to 020 endorse or promote products derived from this software without specific 021 prior written permission. 022 023 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 024 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 025 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 026 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 027 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 028 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 029 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 030 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 031 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 032 POSSIBILITY OF SUCH DAMAGE. 033 034*/ 035 036 037import java.net.URI; 038import java.net.URISyntaxException; 039import java.text.SimpleDateFormat; 040import java.util.Calendar; 041import java.util.Date; 042import java.util.HashMap; 043import java.util.Locale; 044import java.util.Map; 045import java.util.Set; 046import java.util.TimeZone; 047import java.util.regex.Matcher; 048import java.util.regex.Pattern; 049 050import org.apache.commons.lang3.StringUtils; 051import org.apache.http.client.utils.URIBuilder; 052import org.hl7.fhir.r4.model.Resource; 053import org.hl7.fhir.r4.model.ResourceType; 054import org.hl7.fhir.utilities.Utilities; 055 056//Make resources address subclass of URI 057/** 058 * Helper class to manage FHIR Resource URIs 059 * 060 * @author Claude Nanjo 061 * 062 */ 063public class ResourceAddress { 064 065 public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$"; 066 067 private URI baseServiceUri; 068 069 public ResourceAddress(String endpointPath) throws URISyntaxException {//TODO Revisit this exception 070 this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath); 071 } 072 073 public ResourceAddress(URI baseServiceUri) { 074 this.baseServiceUri = baseServiceUri; 075 } 076 077 public URI getBaseServiceUri() { 078 return this.baseServiceUri; 079 } 080 081 public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) { 082 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters); 083 } 084 085 public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) { 086 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters); 087 } 088 089 private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) { 090 String n = nameForClass(resourceClass); 091 return n == null ? "" : n +"/"; 092 } 093 094 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) { 095 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"/"+opName); 096 } 097 098 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) { 099 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters); 100 } 101 102 public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) { 103 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate/"+id); 104 } 105 106 public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) { 107 return baseServiceUri.resolve(nameForClass(resourceClass)); 108 } 109 110 public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) { 111 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id); 112 } 113 114 public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) { 115 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version); 116 } 117 118 public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) { 119 if (canonicalUrl.contains("|")) 120 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1)); 121 else 122 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl); 123 } 124 125 public URI resolveGetHistoryForAllResources(int count) { 126 if(count > 0) { 127 return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count); 128 } else { 129 return baseServiceUri.resolve("_history"); 130 } 131} 132 133 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) { 134 return resolveGetHistoryUriForResourceId(resourceClass, id, null, count); 135 } 136 137 protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) { 138 Map<String,String> parameters = getHistoryParameters(since, count); 139 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters); 140 } 141 142 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) { 143 Map<String,String> parameters = getHistoryParameters(null, count); 144 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 145 } 146 147 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) { 148 Map<String,String> parameters = getHistoryParameters(since, count); 149 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 150 } 151 152 public URI resolveGetHistoryForAllResources(Calendar since, int count) { 153 Map<String,String> parameters = getHistoryParameters(since, count); 154 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 155 } 156 157 public URI resolveGetHistoryForAllResources(Date since, int count) { 158 Map<String,String> parameters = getHistoryParameters(since, count); 159 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 160 } 161 162 public Map<String,String> getHistoryParameters(Object since, int count) { 163 Map<String,String> parameters = new HashMap<String,String>(); 164 if (since != null) { 165 parameters.put("_since", since.toString()); 166 } 167 if(count > 0) { 168 parameters.put("_count", ""+count); 169 } 170 return parameters; 171 } 172 173 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) { 174 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 175 } 176 177 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) { 178 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 179 } 180 181 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) { 182 return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count); 183 } 184 185 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) { 186 return resolveGetHistoryForResourceType(resourceClass, since.toString(), count); 187 } 188 189 public <T extends Resource> URI resolveGetAllTags() { 190 return baseServiceUri.resolve("_tags"); 191 } 192 193 public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) { 194 return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags"); 195 } 196 197 public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) { 198 return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags"); 199 } 200 201 public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 202 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags"); 203 } 204 205 public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 206 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete"); 207 } 208 209 210 public <T extends Resource> String nameForClass(Class<T> resourceClass) { 211 if (resourceClass == null) 212 return null; 213 String res = resourceClass.getSimpleName(); 214 if (res.equals("List_")) 215 return "List"; 216 else 217 return res; 218 } 219 220 public URI resolveMetadataUri(boolean quick) { 221 return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata"); 222 } 223 224 public URI resolveMetadataTxCaps() { 225 return baseServiceUri.resolve("metadata?mode=terminology"); 226 } 227 228 /** 229 * For now, assume this type of location header structure. 230 * Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1 231 * 232 * @param serviceBase 233 * @param locationHeader 234 */ 235 public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) { 236 Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY); 237 Matcher matcher = pattern.matcher(locationResponseHeader); 238 ResourceVersionedIdentifier parsedHeader = null; 239 if(matcher.matches()){ 240 String serviceRoot = matcher.group(1); 241 String resourceType = matcher.group(3); 242 String id = matcher.group(5); 243 String version = matcher.group(7); 244 parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version); 245 } 246 return parsedHeader; 247 } 248 249 public static URI buildAbsoluteURI(String absoluteURI) { 250 251 if(StringUtils.isBlank(absoluteURI)) { 252 throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank")); 253 } 254 255 String endpoint = appendForwardSlashToPath(absoluteURI); 256 257 return buildEndpointUriFromString(endpoint); 258 } 259 260 public static String appendForwardSlashToPath(String path) { 261 if(path.lastIndexOf('/') != path.length() - 1) { 262 path += "/"; 263 } 264 return path; 265 } 266 267 public static URI buildEndpointUriFromString(String endpointPath) { 268 URI uri = null; 269 try { 270 URIBuilder uriBuilder = new URIBuilder(endpointPath); 271 uri = uriBuilder.build(); 272 String scheme = uri.getScheme(); 273 String host = uri.getHost(); 274 if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { 275 throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri); 276 } 277 if(StringUtils.isBlank(host)) { 278 throw new EFhirClientException("host cannot be blank: " + uri); 279 } 280 } catch(URISyntaxException e) { 281 throw new EFhirClientException("Invalid URI", e); 282 } 283 return uri; 284 } 285 286 public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) { 287 URI modifiedUri = null; 288 try { 289 URIBuilder uriBuilder = new URIBuilder(uri); 290 uriBuilder.setQuery(parameterName + "=" + parameterValue); 291 modifiedUri = uriBuilder.build(); 292 } catch(Exception e) { 293 throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e); 294 } 295 return modifiedUri; 296 } 297 298 public static String buildRelativePathFromResourceType(ResourceType resourceType) { 299 //return resourceType.toString().toLowerCase()+"/"; 300 return resourceType.toString() + "/"; 301 } 302 303 public static String buildRelativePathFromResourceType(ResourceType resourceType, String id) { 304 return buildRelativePathFromResourceType(resourceType)+ "@" + id; 305 } 306 307 public static String buildRelativePathFromReference(Resource resource) { 308 return buildRelativePathFromResourceType(resource.getResourceType()); 309 } 310 311 public static String buildRelativePathFromReference(Resource resource, String id) { 312 return buildRelativePathFromResourceType(resource.getResourceType(), id); 313 } 314 315 public static class ResourceVersionedIdentifier { 316 317 private String serviceRoot; 318 private String resourceType; 319 private String id; 320 private String version; 321 private URI resourceLocation; 322 323 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version, URI resourceLocation) { 324 this.serviceRoot = serviceRoot; 325 this.resourceType = resourceType; 326 this.id = id; 327 this.version = version; 328 this.resourceLocation = resourceLocation; 329 } 330 331 public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) { 332 this(null, resourceType, id, version, resourceLocation); 333 } 334 335 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) { 336 this(serviceRoot, resourceType, id, version, null); 337 } 338 339 public ResourceVersionedIdentifier(String resourceType, String id, String version) { 340 this(null, resourceType, id, version, null); 341 } 342 343 public ResourceVersionedIdentifier(String resourceType, String id) { 344 this.id = id; 345 } 346 347 public String getId() { 348 return this.id; 349 } 350 351 protected void setId(String id) { 352 this.id = id; 353 } 354 355 public String getVersionId() { 356 return this.version; 357 } 358 359 protected void setVersionId(String version) { 360 this.version = version; 361 } 362 363 public String getResourceType() { 364 return resourceType; 365 } 366 367 public void setResourceType(String resourceType) { 368 this.resourceType = resourceType; 369 } 370 371 public String getServiceRoot() { 372 return serviceRoot; 373 } 374 375 public void setServiceRoot(String serviceRoot) { 376 this.serviceRoot = serviceRoot; 377 } 378 379 public String getResourcePath() { 380 return this.serviceRoot + "/" + this.resourceType + "/" + this.id; 381 } 382 383 public String getVersion() { 384 return version; 385 } 386 387 public void setVersion(String version) { 388 this.version = version; 389 } 390 391 public URI getResourceLocation() { 392 return this.resourceLocation; 393 } 394 395 public void setResourceLocation(URI resourceLocation) { 396 this.resourceLocation = resourceLocation; 397 } 398 } 399 400 public static String getCalendarDateInIsoTimeFormat(Calendar calendar) { 401 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", new Locale("en", "US"));//TODO Move out 402 format.setTimeZone(TimeZone.getTimeZone("GMT")); 403 return format.format(calendar.getTime()); 404 } 405 406 public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) { 407 Map<String, String> parameters = new HashMap<String, String>(); 408 parameters.put(httpParameterName, httpParameterValue); 409 return appendHttpParameters(basePath, parameters); 410 } 411 412 public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) { 413 try { 414 Set<String> httpParameterNames = parameters.keySet(); 415 String query = basePath.getQuery(); 416 417 for(String httpParameterName : httpParameterNames) { 418 if(query != null) { 419 query += "&"; 420 } else { 421 query = ""; 422 } 423 query += httpParameterName + "=" + Utilities.encodeUri(parameters.get(httpParameterName)); 424 } 425 426 return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment()); 427 } catch(Exception e) { 428 throw new EFhirClientException("Error appending http parameter", e); 429 } 430 } 431 432}