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