
001package org.hl7.fhir.r5.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.nio.charset.StandardCharsets; 040import java.text.SimpleDateFormat; 041import java.util.*; 042import java.util.regex.Matcher; 043import java.util.regex.Pattern; 044 045import org.apache.commons.lang3.StringUtils; 046import org.apache.http.NameValuePair; 047import org.apache.http.client.utils.URIBuilder; 048import org.apache.http.client.utils.URLEncodedUtils; 049import org.hl7.fhir.r5.model.Resource; 050import org.hl7.fhir.r5.model.ResourceType; 051import org.hl7.fhir.utilities.Utilities; 052 053//Make resources address subclass of URI 054/** 055 * Helper class to manage FHIR Resource URIs 056 * 057 * @author Claude Nanjo 058 * 059 */ 060public class ResourceAddress { 061 062 public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$"; 063 064 private URI baseServiceUri; 065 066 public ResourceAddress(String endpointPath) throws URISyntaxException {//TODO Revisit this exception 067 this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath); 068 } 069 070 public ResourceAddress(URI baseServiceUri) { 071 this.baseServiceUri = baseServiceUri; 072 } 073 074 public URI getBaseServiceUri() { 075 return this.baseServiceUri; 076 } 077 078 079 public URI resolveGetUriFromResourceClassAndId(String resourceClass, String id) { 080 return baseServiceUri.resolve(resourceClass + "/" + id); 081 } 082 083 public <T extends Resource> URI resolveGetResource(Class<T> resourceClass, String id) { 084 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "/" + id); 085 } 086 087 public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) { 088 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters); 089 } 090 091 public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) { 092 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters); 093 } 094 095 private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) { 096 String n = nameForClass(resourceClass); 097 return n == null ? "" : n +"/"; 098 } 099 100 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) { 101 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName); 102 } 103 104 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) { 105 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters); 106 } 107 108 @Deprecated 109 public <T extends Resource> URI resolveOperationUriNoEscape(Class<T> resourceClass, String opName, Map<String,String> parameters) { 110 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters); 111 } 112 113 public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) { 114 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate"+(id == null ? "" : "/"+id)); 115 } 116 117 public <T extends Resource> URI resolveValidateUri(String resourceType, String id) { 118 return baseServiceUri.resolve(resourceType +"/$validate"+(id == null ? "" : "/"+id)); 119 } 120 121 public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) { 122 return baseServiceUri.resolve(nameForClass(resourceClass)); 123 } 124 125 public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) { 126 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id); 127 } 128 129 public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) { 130 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version); 131 } 132 133 public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) { 134 if (canonicalUrl.contains("|")) 135 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1)); 136 else 137 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl); 138 } 139 140 public URI resolveGetHistoryForAllResources(int count) { 141 if(count > 0) { 142 return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count); 143 } else { 144 return baseServiceUri.resolve("_history"); 145 } 146} 147 148 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) { 149 return resolveGetHistoryUriForResourceId(resourceClass, id, null, count); 150 } 151 152 protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) { 153 Map<String,String> parameters = getHistoryParameters(since, count); 154 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters); 155 } 156 157 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) { 158 Map<String,String> parameters = getHistoryParameters(null, count); 159 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 160 } 161 162 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) { 163 Map<String,String> parameters = getHistoryParameters(since, count); 164 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 165 } 166 167 public URI resolveGetHistoryForAllResources(Calendar since, int count) { 168 Map<String,String> parameters = getHistoryParameters(since, count); 169 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 170 } 171 172 public URI resolveGetHistoryForAllResources(Date since, int count) { 173 Map<String,String> parameters = getHistoryParameters(since, count); 174 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 175 } 176 177 public Map<String,String> getHistoryParameters(Object since, int count) { 178 Map<String,String> parameters = new HashMap<String,String>(); 179 if (since != null) { 180 parameters.put("_since", since.toString()); 181 } 182 if(count > 0) { 183 parameters.put("_count", ""+count); 184 } 185 return parameters; 186 } 187 188 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) { 189 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 190 } 191 192 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) { 193 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 194 } 195 196 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) { 197 return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count); 198 } 199 200 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) { 201 return resolveGetHistoryForResourceType(resourceClass, since.toString(), count); 202 } 203 204 public <T extends Resource> URI resolveGetAllTags() { 205 return baseServiceUri.resolve("_tags"); 206 } 207 208 public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) { 209 return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags"); 210 } 211 212 public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) { 213 return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags"); 214 } 215 216 public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 217 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags"); 218 } 219 220 public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 221 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete"); 222 } 223 224 225 public <T extends Resource> String nameForClass(Class<T> resourceClass) { 226 if (resourceClass == null) 227 return null; 228 String res = resourceClass.getSimpleName(); 229 if (res.equals("List_")) 230 return "List"; 231 else 232 return res; 233 } 234 235 public URI resolveMetadataUri(boolean quick) { 236 return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata"); 237 } 238 239 public URI resolveMetadataTxCaps() { 240 return baseServiceUri.resolve("metadata?mode=terminology"); 241 } 242 243 /** 244 * For now, assume this type of location header structure. 245 * Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1 246 */ 247 public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) { 248 Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY); 249 Matcher matcher = pattern.matcher(locationResponseHeader); 250 ResourceVersionedIdentifier parsedHeader = null; 251 if(matcher.matches()){ 252 String serviceRoot = matcher.group(1); 253 String resourceType = matcher.group(3); 254 String id = matcher.group(5); 255 String version = matcher.group(7); 256 parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version); 257 } 258 return parsedHeader; 259 } 260 261 public static URI buildAbsoluteURI(String absoluteURI) { 262 263 if(StringUtils.isBlank(absoluteURI)) { 264 throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank")); 265 } 266 267 String endpoint = appendForwardSlashToPath(absoluteURI); 268 269 return buildEndpointUriFromString(endpoint); 270 } 271 272 public static String appendForwardSlashToPath(String path) { 273 if(path.lastIndexOf('/') != path.length() - 1) { 274 path += "/"; 275 } 276 return path; 277 } 278 279 public static URI buildEndpointUriFromString(String endpointPath) { 280 URI uri = null; 281 try { 282 URIBuilder uriBuilder = new URIBuilder(endpointPath); 283 uri = uriBuilder.build(); 284 String scheme = uri.getScheme(); 285 String host = uri.getHost(); 286 if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { 287 throw new EFhirClientException(0, "Scheme must be 'http' or 'https': " + uri); 288 } 289 if(StringUtils.isBlank(host)) { 290 throw new EFhirClientException(0, "host cannot be blank: " + uri); 291 } 292 } catch(URISyntaxException e) { 293 throw new EFhirClientException(0, "Invalid URI", e); 294 } 295 return uri; 296 } 297 298 public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) { 299 URI modifiedUri = null; 300 try { 301 URIBuilder uriBuilder = new URIBuilder(uri); 302 uriBuilder.setQuery(parameterName + "=" + parameterValue); 303 modifiedUri = uriBuilder.build(); 304 } catch(Exception e) { 305 throw new EFhirClientException(0, "Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e); 306 } 307 return modifiedUri; 308 } 309 310 public static String buildRelativePathFromResourceType(ResourceType resourceType) { 311 //return resourceType.toString().toLowerCase()+"/"; 312 return resourceType.toString() + "/"; 313 } 314 315 public static String buildRelativePathFromResourceType(ResourceType resourceType, String id) { 316 return buildRelativePathFromResourceType(resourceType)+ "@" + id; 317 } 318 319 public static String buildRelativePathFromReference(Resource resource) { 320 return buildRelativePathFromResourceType(resource.getResourceType()); 321 } 322 323 public static String buildRelativePathFromReference(Resource resource, String id) { 324 return buildRelativePathFromResourceType(resource.getResourceType(), id); 325 } 326 327 public static class ResourceVersionedIdentifier { 328 329 private String serviceRoot; 330 private String resourceType; 331 private String id; 332 private String version; 333 private URI resourceLocation; 334 335 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version, URI resourceLocation) { 336 this.serviceRoot = serviceRoot; 337 this.resourceType = resourceType; 338 this.id = id; 339 this.version = version; 340 this.resourceLocation = resourceLocation; 341 } 342 343 public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) { 344 this(null, resourceType, id, version, resourceLocation); 345 } 346 347 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) { 348 this(serviceRoot, resourceType, id, version, null); 349 } 350 351 public ResourceVersionedIdentifier(String resourceType, String id, String version) { 352 this(null, resourceType, id, version, null); 353 } 354 355 public ResourceVersionedIdentifier(String resourceType, String id) { 356 this.id = id; 357 } 358 359 public String getId() { 360 return this.id; 361 } 362 363 protected void setId(String id) { 364 this.id = id; 365 } 366 367 public String getVersionId() { 368 return this.version; 369 } 370 371 protected void setVersionId(String version) { 372 this.version = version; 373 } 374 375 public String getResourceType() { 376 return resourceType; 377 } 378 379 public void setResourceType(String resourceType) { 380 this.resourceType = resourceType; 381 } 382 383 public String getServiceRoot() { 384 return serviceRoot; 385 } 386 387 public void setServiceRoot(String serviceRoot) { 388 this.serviceRoot = serviceRoot; 389 } 390 391 public String getResourcePath() { 392 return this.serviceRoot + "/" + this.resourceType + "/" + this.id; 393 } 394 395 public String getVersion() { 396 return version; 397 } 398 399 public void setVersion(String version) { 400 this.version = version; 401 } 402 403 public URI getResourceLocation() { 404 return this.resourceLocation; 405 } 406 407 public void setResourceLocation(URI resourceLocation) { 408 this.resourceLocation = resourceLocation; 409 } 410 } 411 412 public static String getCalendarDateInIsoTimeFormat(Calendar calendar) { 413 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", new Locale("en", "US"));//TODO Move out 414 format.setTimeZone(TimeZone.getTimeZone("GMT")); 415 return format.format(calendar.getTime()); 416 } 417 418 public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) { 419 Map<String, String> parameters = new HashMap<String, String>(); 420 parameters.put(httpParameterName, httpParameterValue); 421 return appendHttpParameters(basePath, parameters); 422 } 423 424 public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) { 425 try { 426 List<NameValuePair> existingParams = URLEncodedUtils.parse(basePath.getQuery(), StandardCharsets.UTF_8); 427 428 URIBuilder uriBuilder = new URIBuilder() 429 .setScheme(basePath.getScheme()) 430 .setHost(basePath.getHost()) 431 .setPort(basePath.getPort()) 432 .setUserInfo(basePath.getUserInfo()) 433 .setFragment(basePath.getFragment()); 434 for (NameValuePair pair : existingParams) { 435 uriBuilder.addParameter(pair.getName(), pair.getValue()); 436 } 437 for (Map.Entry<String, String> entry : parameters.entrySet()) { 438 uriBuilder.addParameter(entry.getKey(), entry.getValue()); 439 } 440 uriBuilder.setPath(basePath.getPath()); 441 442 return uriBuilder.build(); 443 } catch(Exception e) { 444 throw new EFhirClientException(0, "Error appending http parameter", e); 445 } 446 } 447 448}