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