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