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 */
058@Deprecated
059public class ResourceAddress {
060
061  public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$";
062
063  private URI baseServiceUri;
064
065  public ResourceAddress(String endpointPath) throws URISyntaxException {// TODO Revisit this exception
066    this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath);
067  }
068
069  public ResourceAddress(URI baseServiceUri) {
070    this.baseServiceUri = baseServiceUri;
071  }
072
073  public URI getBaseServiceUri() {
074    return this.baseServiceUri;
075  }
076
077  public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) {
078    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "$" + name + "?" + parameters);
079  }
080
081  public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String, String> parameters) {
082    return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "_search"), parameters);
083  }
084
085  private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) {
086    String n = nameForClass(resourceClass);
087    return n == null ? "" : n + "/";
088  }
089
090  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) {
091    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "$" + opName);
092  }
093
094  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName,
095      Map<String, String> parameters) {
096    return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "$" + opName),
097        parameters);
098  }
099
100  public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) {
101    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "$validate/" + id);
102  }
103
104  public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) {
105    return baseServiceUri.resolve(nameForClass(resourceClass));
106  }
107
108  public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) {
109    return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id);
110  }
111
112  public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id,
113      String version) {
114    return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history/" + version);
115  }
116
117  public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass,
118      String canonicalUrl) {
119    if (canonicalUrl.contains("|"))
120      return baseServiceUri
121          .resolve(nameForClass(resourceClass) + "?url=" + canonicalUrl.substring(0, canonicalUrl.indexOf("|"))
122              + "&version=" + canonicalUrl.substring(canonicalUrl.indexOf("|") + 1));
123    else
124      return baseServiceUri.resolve(nameForClass(resourceClass) + "?url=" + canonicalUrl);
125  }
126
127  public URI resolveGetHistoryForAllResources(int count) {
128    if (count > 0) {
129      return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", "" + count);
130    } else {
131      return baseServiceUri.resolve("_history");
132    }
133  }
134
135  public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) {
136    return resolveGetHistoryUriForResourceId(resourceClass, id, null, count);
137  }
138
139  protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since,
140      int count) {
141    Map<String, String> parameters = getHistoryParameters(since, count);
142    return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"),
143        parameters);
144  }
145
146  public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) {
147    Map<String, String> parameters = getHistoryParameters(null, count);
148    return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
149  }
150
151  public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) {
152    Map<String, String> parameters = getHistoryParameters(since, count);
153    return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
154  }
155
156  public URI resolveGetHistoryForAllResources(Calendar since, int count) {
157    Map<String, String> parameters = getHistoryParameters(since, count);
158    return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
159  }
160
161  public URI resolveGetHistoryForAllResources(Date since, int count) {
162    Map<String, String> parameters = getHistoryParameters(since, count);
163    return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
164  }
165
166  public Map<String, String> getHistoryParameters(Object since, int count) {
167    Map<String, String> parameters = new HashMap<String, String>();
168    if (since != null) {
169      parameters.put("_since", since.toString());
170    }
171    if (count > 0) {
172      parameters.put("_count", "" + count);
173    }
174    return parameters;
175  }
176
177  public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since,
178      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,
183      int count) {
184    return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
185  }
186
187  public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) {
188    return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count);
189  }
190
191  public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) {
192    return resolveGetHistoryForResourceType(resourceClass, since.toString(), count);
193  }
194
195  public <T extends Resource> URI resolveGetAllTags() {
196    return baseServiceUri.resolve("_tags");
197  }
198
199  public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) {
200    return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags");
201  }
202
203  public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) {
204    return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags");
205  }
206
207  public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
208    return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history/" + version + "/_tags");
209  }
210
211  public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id,
212      String version) {
213    return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history/" + version + "/_tags/_delete");
214  }
215
216  public <T extends Resource> String nameForClass(Class<T> resourceClass) {
217    if (resourceClass == null)
218      return null;
219    String res = resourceClass.getSimpleName();
220    if (res.equals("List_"))
221      return "List";
222    else
223      return res;
224  }
225
226  public URI resolveMetadataUri(boolean quick) {
227    return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
228  }
229
230  public URI resolveMetadataTxCaps() {
231    return baseServiceUri.resolve("metadata?mode=terminology");
232  }
233
234  /**
235   * For now, assume this type of location header structure. Generalize later:
236   * http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1
237   *
238   * @param locationResponseHeader
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.encodeUriParam(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}