001package org.hl7.fhir.r5.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.Locale;
044import java.util.Map;
045import java.util.Set;
046import java.util.TimeZone;
047import java.util.regex.Matcher;
048import java.util.regex.Pattern;
049
050import org.apache.commons.lang3.StringUtils;
051import org.apache.http.client.utils.URIBuilder;
052import org.hl7.fhir.r5.model.Resource;
053import org.hl7.fhir.r5.model.ResourceType;
054import org.hl7.fhir.utilities.Utilities;
055
056//Make resources address subclass of URI
057/**
058 * Helper class to manage FHIR Resource URIs
059 * 
060 * @author Claude Nanjo
061 *
062 */
063public class ResourceAddress {
064        
065        public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$";
066        
067        private URI baseServiceUri;
068        
069        public ResourceAddress(String endpointPath) throws URISyntaxException {//TODO Revisit this exception
070                this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath);
071        }
072        
073        public ResourceAddress(URI baseServiceUri) {
074                this.baseServiceUri = baseServiceUri;
075        }
076        
077        public URI getBaseServiceUri() {
078                return this.baseServiceUri;
079        }
080        
081
082  public URI resolveGetUriFromResourceClassAndId(String resourceClass, String id) {
083    return baseServiceUri.resolve(resourceClass + "/" + id);
084  }
085
086  public <T extends Resource> URI resolveGetResource(Class<T> resourceClass, String id) {
087    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) + "/" + id);
088  }
089  
090        public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) {
091                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters);
092        }
093        
094        public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) {
095                return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters);
096        }
097        
098  private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) {
099    String n = nameForClass(resourceClass);
100    return n == null ? "" : n +"/";
101        }
102        
103  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) {
104    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName);
105  }
106  
107  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) {
108    return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters);
109  }
110  
111        public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) {
112                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate/"+id);
113        }
114        
115        public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) {
116                return baseServiceUri.resolve(nameForClass(resourceClass));
117        }
118        
119        public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) {
120                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id);
121        }
122        
123        public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) {
124                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version);
125        }
126        
127  public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) {
128    if (canonicalUrl.contains("|"))
129      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1));      
130    else
131      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl);
132  }
133  
134        public URI resolveGetHistoryForAllResources(int count) {
135                if(count > 0) {
136                        return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count);
137                } else {
138                        return baseServiceUri.resolve("_history");
139                }
140}
141        
142        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) {
143                return resolveGetHistoryUriForResourceId(resourceClass, id, null, count);
144        }
145        
146        protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) {
147                Map<String,String>  parameters = getHistoryParameters(since, count);
148                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters);
149        }
150        
151        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) {
152                Map<String,String>  parameters = getHistoryParameters(null, count);
153                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
154        }
155        
156        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) {
157                Map<String,String>  parameters = getHistoryParameters(since, count);
158                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
159        }
160        
161        public URI resolveGetHistoryForAllResources(Calendar since, int count) {
162                Map<String,String>  parameters = getHistoryParameters(since, count);
163                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
164        }
165        
166        public URI resolveGetHistoryForAllResources(Date since, int count) {
167                Map<String,String>  parameters = getHistoryParameters(since, count);
168                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
169        }
170        
171        public Map<String,String> getHistoryParameters(Object since, int count) {
172                Map<String,String>  parameters = new HashMap<String,String>();
173                if (since != null) {
174                        parameters.put("_since", since.toString());
175                }
176                if(count > 0) {
177                        parameters.put("_count", ""+count);
178                }
179                return parameters;
180        }
181        
182        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) {
183                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
184        }
185        
186        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) {
187                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
188        }
189        
190        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) {
191                return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count);
192        }
193        
194        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) {
195                return resolveGetHistoryForResourceType(resourceClass, since.toString(), count);
196        }
197        
198        public <T extends Resource> URI resolveGetAllTags() {
199                return baseServiceUri.resolve("_tags");
200        }
201        
202        public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) {
203                return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags");
204        }
205        
206        public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) {
207                return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags");
208        }
209        
210        public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
211                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags");
212        }
213        
214        public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
215                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete");
216        }
217        
218
219        public <T extends Resource> String nameForClass(Class<T> resourceClass) {
220          if (resourceClass == null)
221            return null;
222                String res = resourceClass.getSimpleName();
223                if (res.equals("List_"))
224                        return "List";
225                else
226                        return res;
227        }
228        
229  public URI resolveMetadataUri(boolean quick) {
230    return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
231  }
232  
233  public URI resolveMetadataTxCaps() {
234    return baseServiceUri.resolve("metadata?mode=terminology");
235  }
236  
237        /**
238         * For now, assume this type of location header structure.
239         * Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1
240         */
241        public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
242                Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
243                Matcher matcher = pattern.matcher(locationResponseHeader);
244                ResourceVersionedIdentifier parsedHeader = null;
245                if(matcher.matches()){
246                        String serviceRoot = matcher.group(1);
247                        String resourceType = matcher.group(3);
248                        String id = matcher.group(5);
249                        String version = matcher.group(7);
250                        parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version);
251                }
252                return parsedHeader;
253        }
254        
255        public static URI buildAbsoluteURI(String absoluteURI) {
256                
257                if(StringUtils.isBlank(absoluteURI)) {
258                        throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
259                } 
260                
261                String endpoint = appendForwardSlashToPath(absoluteURI);
262
263                return buildEndpointUriFromString(endpoint);
264        }
265        
266        public static String appendForwardSlashToPath(String path) {
267                if(path.lastIndexOf('/') != path.length() - 1) {
268                        path += "/";
269                }
270                return path;
271        }
272        
273        public static URI buildEndpointUriFromString(String endpointPath) {
274                URI uri = null; 
275                try {
276                        URIBuilder uriBuilder = new URIBuilder(endpointPath);
277                        uri = uriBuilder.build();
278                        String scheme = uri.getScheme();
279                        String host = uri.getHost();
280                        if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
281                                throw new EFhirClientException(0, "Scheme must be 'http' or 'https': " + uri);
282                        }
283                        if(StringUtils.isBlank(host)) {
284                                throw new EFhirClientException(0, "host cannot be blank: " + uri);
285                        }
286                } catch(URISyntaxException e) {
287                        throw new EFhirClientException(0, "Invalid URI", e);
288                }
289                return uri;
290        }
291        
292        public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) {
293                URI modifiedUri = null;
294                try {
295                        URIBuilder uriBuilder = new URIBuilder(uri);
296                        uriBuilder.setQuery(parameterName + "=" + parameterValue);
297                        modifiedUri = uriBuilder.build();
298                } catch(Exception e) {
299                        throw new EFhirClientException(0, "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, URI resourceLocation) {
330                        this.serviceRoot = serviceRoot;
331                        this.resourceType = resourceType;
332                        this.id = id;
333                        this.version = version;
334                        this.resourceLocation = resourceLocation;
335                }
336                
337                public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) {
338                        this(null, resourceType, id, version, resourceLocation);
339                }
340                
341                public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) {
342                        this(serviceRoot, resourceType, id, version, null);
343                }
344                
345                public ResourceVersionedIdentifier(String resourceType, String id, String version) {
346                        this(null, resourceType, id, version, null);
347                }
348                
349                public ResourceVersionedIdentifier(String resourceType, String id) {
350                        this.id = id;
351                }
352                
353                public String getId() {
354                        return this.id;
355                }
356                
357                protected void setId(String id) {
358                        this.id = id;
359                }
360                
361                public String getVersionId() {
362                        return this.version;
363                }
364                
365                protected void setVersionId(String version) {
366                        this.version = version;
367                }
368                
369                public String getResourceType() {
370                        return resourceType;
371                }
372
373                public void setResourceType(String resourceType) {
374                        this.resourceType = resourceType;
375                }
376                
377                public String getServiceRoot() {
378                        return serviceRoot;
379                }
380
381                public void setServiceRoot(String serviceRoot) {
382                        this.serviceRoot = serviceRoot;
383                }
384                
385                public String getResourcePath() {
386                        return this.serviceRoot + "/" + this.resourceType + "/" + this.id;
387                }
388
389                public String getVersion() {
390                        return version;
391                }
392
393                public void setVersion(String version) {
394                        this.version = version;
395                }
396
397                public URI getResourceLocation() {
398                        return this.resourceLocation;
399                }
400                
401                public void setResourceLocation(URI resourceLocation) {
402                        this.resourceLocation = resourceLocation;
403                }
404        }
405        
406        public static String getCalendarDateInIsoTimeFormat(Calendar calendar) {
407                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", new Locale("en", "US"));//TODO Move out
408                format.setTimeZone(TimeZone.getTimeZone("GMT"));
409            return format.format(calendar.getTime());
410        }
411        
412        public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) {
413                Map<String, String> parameters = new HashMap<String, String>();
414                parameters.put(httpParameterName, httpParameterValue);
415                return appendHttpParameters(basePath, parameters);
416        }
417        
418        public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) {
419        try {
420                Set<String> httpParameterNames = parameters.keySet();
421                String query = basePath.getQuery();
422                
423                for(String httpParameterName : httpParameterNames) {
424                        if(query != null) {
425                                query += "&";
426                        } else {
427                                query = "";
428                        }
429                        query += httpParameterName + "=" + Utilities.encodeUri(parameters.get(httpParameterName));
430                }
431        
432                return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment());
433        } catch(Exception e) {
434                throw new EFhirClientException(0, "Error appending http parameter", e);
435        }
436    }
437        
438}