001package org.hl7.fhir.r4.utils.client;
002
003
004
005
006
007/*
008  Copyright (c) 2011+, HL7, Inc.
009  All rights reserved.
010  
011  Redistribution and use in source and binary forms, with or without modification, 
012  are permitted provided that the following conditions are met:
013  
014   * Redistributions of source code must retain the above copyright notice, this 
015     list of conditions and the following disclaimer.
016   * Redistributions in binary form must reproduce the above copyright notice, 
017     this list of conditions and the following disclaimer in the documentation 
018     and/or other materials provided with the distribution.
019   * Neither the name of HL7 nor the names of its contributors may be used to 
020     endorse or promote products derived from this software without specific 
021     prior written permission.
022  
023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
026  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
027  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
028  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
029  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
030  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
031  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
032  POSSIBILITY OF SUCH DAMAGE.
033  
034*/
035
036
037import java.net.URI;
038import java.net.URISyntaxException;
039import java.text.SimpleDateFormat;
040import java.util.Calendar;
041import java.util.Date;
042import java.util.HashMap;
043import java.util.Map;
044import java.util.Set;
045import java.util.TimeZone;
046import java.util.regex.Matcher;
047import java.util.regex.Pattern;
048
049import org.apache.commons.lang3.StringUtils;
050import org.apache.http.client.utils.URIBuilder;
051import org.hl7.fhir.r4.model.Resource;
052import org.hl7.fhir.r4.model.ResourceType;
053import org.hl7.fhir.utilities.Utilities;
054
055//Make resources address subclass of URI
056/**
057 * Helper class to manage FHIR Resource URIs
058 * 
059 * @author Claude Nanjo
060 *
061 */
062public class ResourceAddress {
063        
064        public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$";
065        
066        private URI baseServiceUri;
067        
068        public ResourceAddress(String endpointPath) throws URISyntaxException {//TODO Revisit this exception
069                this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath);
070        }
071        
072        public ResourceAddress(URI baseServiceUri) {
073                this.baseServiceUri = baseServiceUri;
074        }
075        
076        public URI getBaseServiceUri() {
077                return this.baseServiceUri;
078        }
079        
080        public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) {
081                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters);
082        }
083        
084        public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) {
085                return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters);
086        }
087        
088  private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) {
089    String n = nameForClass(resourceClass);
090    return n == null ? "" : n +"/";
091        }
092        
093  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) {
094    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"/"+opName);
095  }
096  
097  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) {
098    return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters);
099  }
100  
101        public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) {
102                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate/"+id);
103        }
104        
105        public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) {
106                return baseServiceUri.resolve(nameForClass(resourceClass));
107        }
108        
109        public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) {
110                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id);
111        }
112        
113        public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) {
114                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version);
115        }
116        
117  public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) {
118    if (canonicalUrl.contains("|"))
119      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1));      
120    else
121      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl);
122  }
123  
124        public URI resolveGetHistoryForAllResources(int count) {
125                if(count > 0) {
126                        return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count);
127                } else {
128                        return baseServiceUri.resolve("_history");
129                }
130}
131        
132        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) {
133                return resolveGetHistoryUriForResourceId(resourceClass, id, null, count);
134        }
135        
136        protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) {
137                Map<String,String>  parameters = getHistoryParameters(since, count);
138                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters);
139        }
140        
141        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) {
142                Map<String,String>  parameters = getHistoryParameters(null, count);
143                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
144        }
145        
146        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) {
147                Map<String,String>  parameters = getHistoryParameters(since, count);
148                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
149        }
150        
151        public URI resolveGetHistoryForAllResources(Calendar since, int count) {
152                Map<String,String>  parameters = getHistoryParameters(since, count);
153                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
154        }
155        
156        public URI resolveGetHistoryForAllResources(Date since, int count) {
157                Map<String,String>  parameters = getHistoryParameters(since, count);
158                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
159        }
160        
161        public Map<String,String> getHistoryParameters(Object since, int count) {
162                Map<String,String>  parameters = new HashMap<String,String>();
163                if (since != null) {
164                        parameters.put("_since", since.toString());
165                }
166                if(count > 0) {
167                        parameters.put("_count", ""+count);
168                }
169                return parameters;
170        }
171        
172        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) {
173                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
174        }
175        
176        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) {
177                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
178        }
179        
180        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) {
181                return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count);
182        }
183        
184        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) {
185                return resolveGetHistoryForResourceType(resourceClass, since.toString(), count);
186        }
187        
188        public <T extends Resource> URI resolveGetAllTags() {
189                return baseServiceUri.resolve("_tags");
190        }
191        
192        public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) {
193                return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags");
194        }
195        
196        public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) {
197                return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags");
198        }
199        
200        public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
201                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags");
202        }
203        
204        public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
205                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete");
206        }
207        
208
209        public <T extends Resource> String nameForClass(Class<T> resourceClass) {
210          if (resourceClass == null)
211            return null;
212                String res = resourceClass.getSimpleName();
213                if (res.equals("List_"))
214                        return "List";
215                else
216                        return res;
217        }
218        
219  public URI resolveMetadataUri(boolean quick) {
220    return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
221  }
222  
223  public URI resolveMetadataTxCaps() {
224    return baseServiceUri.resolve("metadata?mode=terminology");
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}