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