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