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