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