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}