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