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