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