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