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