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