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