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