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