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