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