001package org.hl7.fhir.r4.utils.client; 002 003 004/* 005 Copyright (c) 2011+, HL7, Inc. 006 All rights reserved. 007 008 Redistribution and use in source and binary forms, with or without modification, 009 are permitted provided that the following conditions are met: 010 011 * Redistributions of source code must retain the above copyright notice, this 012 list of conditions and the following disclaimer. 013 * Redistributions in binary form must reproduce the above copyright notice, 014 this list of conditions and the following disclaimer in the documentation 015 and/or other materials provided with the distribution. 016 * Neither the name of HL7 nor the names of its contributors may be used to 017 endorse or promote products derived from this software without specific 018 prior written permission. 019 020 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 021 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 022 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 023 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 024 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 025 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 026 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 027 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 029 POSSIBILITY OF SUCH DAMAGE. 030 031*/ 032 033 034import java.net.URI; 035import java.net.URISyntaxException; 036import java.text.SimpleDateFormat; 037import java.util.Calendar; 038import java.util.Date; 039import java.util.HashMap; 040import java.util.Map; 041import java.util.Set; 042import java.util.TimeZone; 043import java.util.regex.Matcher; 044import java.util.regex.Pattern; 045 046import org.apache.commons.lang3.StringUtils; 047import org.apache.http.client.utils.URIBuilder; 048import org.fhir.ucum.Canonical; 049import org.hl7.fhir.r4.model.Resource; 050import org.hl7.fhir.r4.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 public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) { 079 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters); 080 } 081 082 public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) { 083 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters); 084 } 085 086 private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) { 087 String n = nameForClass(resourceClass); 088 return n == null ? "" : n +"/"; 089 } 090 091 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) { 092 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"/"+opName); 093 } 094 095 public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) { 096 return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters); 097 } 098 099 public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) { 100 return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate/"+id); 101 } 102 103 public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) { 104 return baseServiceUri.resolve(nameForClass(resourceClass)); 105 } 106 107 public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) { 108 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id); 109 } 110 111 public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) { 112 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version); 113 } 114 115 public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) { 116 if (canonicalUrl.contains("|")) 117 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1)); 118 else 119 return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl); 120 } 121 122 public URI resolveGetHistoryForAllResources(int count) { 123 if(count > 0) { 124 return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count); 125 } else { 126 return baseServiceUri.resolve("_history"); 127 } 128} 129 130 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) { 131 return resolveGetHistoryUriForResourceId(resourceClass, id, null, count); 132 } 133 134 protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) { 135 Map<String,String> parameters = getHistoryParameters(since, count); 136 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters); 137 } 138 139 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) { 140 Map<String,String> parameters = getHistoryParameters(null, count); 141 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 142 } 143 144 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) { 145 Map<String,String> parameters = getHistoryParameters(since, count); 146 return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters); 147 } 148 149 public URI resolveGetHistoryForAllResources(Calendar since, int count) { 150 Map<String,String> parameters = getHistoryParameters(since, count); 151 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 152 } 153 154 public URI resolveGetHistoryForAllResources(Date since, int count) { 155 Map<String,String> parameters = getHistoryParameters(since, count); 156 return appendHttpParameters(baseServiceUri.resolve("_history"), parameters); 157 } 158 159 public Map<String,String> getHistoryParameters(Object since, int count) { 160 Map<String,String> parameters = new HashMap<String,String>(); 161 if (since != null) { 162 parameters.put("_since", since.toString()); 163 } 164 if(count > 0) { 165 parameters.put("_count", ""+count); 166 } 167 return parameters; 168 } 169 170 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) { 171 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 172 } 173 174 public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) { 175 return resolveGetHistoryUriForResourceId(resourceClass, id, since, count); 176 } 177 178 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) { 179 return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count); 180 } 181 182 public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) { 183 return resolveGetHistoryForResourceType(resourceClass, since.toString(), count); 184 } 185 186 public <T extends Resource> URI resolveGetAllTags() { 187 return baseServiceUri.resolve("_tags"); 188 } 189 190 public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) { 191 return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags"); 192 } 193 194 public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) { 195 return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags"); 196 } 197 198 public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 199 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags"); 200 } 201 202 public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) { 203 return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete"); 204 } 205 206 207 public <T extends Resource> String nameForClass(Class<T> resourceClass) { 208 if (resourceClass == null) 209 return null; 210 String res = resourceClass.getSimpleName(); 211 if (res.equals("List_")) 212 return "List"; 213 else 214 return res; 215 } 216 217 public URI resolveMetadataUri(boolean quick) { 218 return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata"); 219 } 220 221 /** 222 * For now, assume this type of location header structure. 223 * Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1 224 * 225 * @param serviceBase 226 * @param locationHeader 227 */ 228 public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) { 229 Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY); 230 Matcher matcher = pattern.matcher(locationResponseHeader); 231 ResourceVersionedIdentifier parsedHeader = null; 232 if(matcher.matches()){ 233 String serviceRoot = matcher.group(1); 234 String resourceType = matcher.group(3); 235 String id = matcher.group(5); 236 String version = matcher.group(7); 237 parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version); 238 } 239 return parsedHeader; 240 } 241 242 public static URI buildAbsoluteURI(String absoluteURI) { 243 244 if(StringUtils.isBlank(absoluteURI)) { 245 throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank")); 246 } 247 248 String endpoint = appendForwardSlashToPath(absoluteURI); 249 250 return buildEndpointUriFromString(endpoint); 251 } 252 253 public static String appendForwardSlashToPath(String path) { 254 if(path.lastIndexOf('/') != path.length() - 1) { 255 path += "/"; 256 } 257 return path; 258 } 259 260 public static URI buildEndpointUriFromString(String endpointPath) { 261 URI uri = null; 262 try { 263 URIBuilder uriBuilder = new URIBuilder(endpointPath); 264 uri = uriBuilder.build(); 265 String scheme = uri.getScheme(); 266 String host = uri.getHost(); 267 if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) { 268 throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri); 269 } 270 if(StringUtils.isBlank(host)) { 271 throw new EFhirClientException("host cannot be blank: " + uri); 272 } 273 } catch(URISyntaxException e) { 274 throw new EFhirClientException("Invalid URI", e); 275 } 276 return uri; 277 } 278 279 public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) { 280 URI modifiedUri = null; 281 try { 282 URIBuilder uriBuilder = new URIBuilder(uri); 283 uriBuilder.setQuery(parameterName + "=" + parameterValue); 284 modifiedUri = uriBuilder.build(); 285 } catch(Exception e) { 286 throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e); 287 } 288 return modifiedUri; 289 } 290 291 public static String buildRelativePathFromResourceType(ResourceType resourceType) { 292 //return resourceType.toString().toLowerCase()+"/"; 293 return resourceType.toString() + "/"; 294 } 295 296 public static String buildRelativePathFromResourceType(ResourceType resourceType, String id) { 297 return buildRelativePathFromResourceType(resourceType)+ "@" + id; 298 } 299 300 public static String buildRelativePathFromReference(Resource resource) { 301 return buildRelativePathFromResourceType(resource.getResourceType()); 302 } 303 304 public static String buildRelativePathFromReference(Resource resource, String id) { 305 return buildRelativePathFromResourceType(resource.getResourceType(), id); 306 } 307 308 public static class ResourceVersionedIdentifier { 309 310 private String serviceRoot; 311 private String resourceType; 312 private String id; 313 private String version; 314 private URI resourceLocation; 315 316 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version, URI resourceLocation) { 317 this.serviceRoot = serviceRoot; 318 this.resourceType = resourceType; 319 this.id = id; 320 this.version = version; 321 this.resourceLocation = resourceLocation; 322 } 323 324 public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) { 325 this(null, resourceType, id, version, resourceLocation); 326 } 327 328 public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) { 329 this(serviceRoot, resourceType, id, version, null); 330 } 331 332 public ResourceVersionedIdentifier(String resourceType, String id, String version) { 333 this(null, resourceType, id, version, null); 334 } 335 336 public ResourceVersionedIdentifier(String resourceType, String id) { 337 this.id = id; 338 } 339 340 public String getId() { 341 return this.id; 342 } 343 344 protected void setId(String id) { 345 this.id = id; 346 } 347 348 public String getVersionId() { 349 return this.version; 350 } 351 352 protected void setVersionId(String version) { 353 this.version = version; 354 } 355 356 public String getResourceType() { 357 return resourceType; 358 } 359 360 public void setResourceType(String resourceType) { 361 this.resourceType = resourceType; 362 } 363 364 public String getServiceRoot() { 365 return serviceRoot; 366 } 367 368 public void setServiceRoot(String serviceRoot) { 369 this.serviceRoot = serviceRoot; 370 } 371 372 public String getResourcePath() { 373 return this.serviceRoot + "/" + this.resourceType + "/" + this.id; 374 } 375 376 public String getVersion() { 377 return version; 378 } 379 380 public void setVersion(String version) { 381 this.version = version; 382 } 383 384 public URI getResourceLocation() { 385 return this.resourceLocation; 386 } 387 388 public void setResourceLocation(URI resourceLocation) { 389 this.resourceLocation = resourceLocation; 390 } 391 } 392 393 public static String getCalendarDateInIsoTimeFormat(Calendar calendar) { 394 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");//TODO Move out 395 format.setTimeZone(TimeZone.getTimeZone("GMT")); 396 return format.format(calendar.getTime()); 397 } 398 399 public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) { 400 Map<String, String> parameters = new HashMap<String, String>(); 401 parameters.put(httpParameterName, httpParameterValue); 402 return appendHttpParameters(basePath, parameters); 403 } 404 405 public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) { 406 try { 407 Set<String> httpParameterNames = parameters.keySet(); 408 String query = basePath.getQuery(); 409 410 for(String httpParameterName : httpParameterNames) { 411 if(query != null) { 412 query += "&"; 413 } else { 414 query = ""; 415 } 416 query += httpParameterName + "=" + Utilities.encodeUri(parameters.get(httpParameterName)); 417 } 418 419 return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment()); 420 } catch(Exception e) { 421 throw new EFhirClientException("Error appending http parameter", e); 422 } 423 } 424 425}