001package org.hl7.fhir.dstu3.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.dstu3.model.Resource;
053import org.hl7.fhir.dstu3.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        /**
230         * For now, assume this type of location header structure.
231         * Generalize later: http://hl7connect.healthintersections.com.au/svc/fhir/318/_history/1
232         * 
233         * @param serviceBase
234         * @param locationHeader
235         */
236        public static ResourceAddress.ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
237                Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
238                Matcher matcher = pattern.matcher(locationResponseHeader);
239                ResourceVersionedIdentifier parsedHeader = null;
240                if(matcher.matches()){
241                        String serviceRoot = matcher.group(1);
242                        String resourceType = matcher.group(3);
243                        String id = matcher.group(5);
244                        String version = matcher.group(7);
245                        parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version);
246                }
247                return parsedHeader;
248        }
249        
250        public static URI buildAbsoluteURI(String absoluteURI) {
251                
252                if(StringUtils.isBlank(absoluteURI)) {
253                        throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
254                } 
255                
256                String endpoint = appendForwardSlashToPath(absoluteURI);
257
258                return buildEndpointUriFromString(endpoint);
259        }
260        
261        public static String appendForwardSlashToPath(String path) {
262                if(path.lastIndexOf('/') != path.length() - 1) {
263                        path += "/";
264                }
265                return path;
266        }
267        
268        public static URI buildEndpointUriFromString(String endpointPath) {
269                URI uri = null; 
270                try {
271                        URIBuilder uriBuilder = new URIBuilder(endpointPath);
272                        uri = uriBuilder.build();
273                        String scheme = uri.getScheme();
274                        String host = uri.getHost();
275                        if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
276                                throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri);
277                        }
278                        if(StringUtils.isBlank(host)) {
279                                throw new EFhirClientException("host cannot be blank: " + uri);
280                        }
281                } catch(URISyntaxException e) {
282                        throw new EFhirClientException("Invalid URI", e);
283                }
284                return uri;
285        }
286        
287        public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) {
288                URI modifiedUri = null;
289                try {
290                        URIBuilder uriBuilder = new URIBuilder(uri);
291                        uriBuilder.setQuery(parameterName + "=" + parameterValue);
292                        modifiedUri = uriBuilder.build();
293                } catch(Exception e) {
294                        throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
295                }
296                return modifiedUri;
297        }
298        
299        public static String buildRelativePathFromResourceType(ResourceType resourceType) {
300                //return resourceType.toString().toLowerCase()+"/";
301                return resourceType.toString() + "/";
302        }
303        
304        public static String buildRelativePathFromResourceType(ResourceType resourceType, String id) {
305                return buildRelativePathFromResourceType(resourceType)+ "@" + id;
306        }
307        
308        public static String buildRelativePathFromReference(Resource resource) {
309                return buildRelativePathFromResourceType(resource.getResourceType());
310        }
311        
312        public static String buildRelativePathFromReference(Resource resource, String id) {
313                return buildRelativePathFromResourceType(resource.getResourceType(), id);
314        }
315        
316        public static class ResourceVersionedIdentifier {
317                
318                private String serviceRoot;
319                private String resourceType;
320                private String id;
321                private String version;
322                private URI resourceLocation;
323                
324                public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version, URI resourceLocation) {
325                        this.serviceRoot = serviceRoot;
326                        this.resourceType = resourceType;
327                        this.id = id;
328                        this.version = version;
329                        this.resourceLocation = resourceLocation;
330                }
331                
332                public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) {
333                        this(null, resourceType, id, version, resourceLocation);
334                }
335                
336                public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) {
337                        this(serviceRoot, resourceType, id, version, null);
338                }
339                
340                public ResourceVersionedIdentifier(String resourceType, String id, String version) {
341                        this(null, resourceType, id, version, null);
342                }
343                
344                public ResourceVersionedIdentifier(String resourceType, String id) {
345                        this.id = id;
346                }
347                
348                public String getId() {
349                        return this.id;
350                }
351                
352                protected void setId(String id) {
353                        this.id = id;
354                }
355                
356                public String getVersionId() {
357                        return this.version;
358                }
359                
360                protected void setVersionId(String version) {
361                        this.version = version;
362                }
363                
364                public String getResourceType() {
365                        return resourceType;
366                }
367
368                public void setResourceType(String resourceType) {
369                        this.resourceType = resourceType;
370                }
371                
372                public String getServiceRoot() {
373                        return serviceRoot;
374                }
375
376                public void setServiceRoot(String serviceRoot) {
377                        this.serviceRoot = serviceRoot;
378                }
379                
380                public String getResourcePath() {
381                        return this.serviceRoot + "/" + this.resourceType + "/" + this.id;
382                }
383
384                public String getVersion() {
385                        return version;
386                }
387
388                public void setVersion(String version) {
389                        this.version = version;
390                }
391
392                public URI getResourceLocation() {
393                        return this.resourceLocation;
394                }
395                
396                public void setResourceLocation(URI resourceLocation) {
397                        this.resourceLocation = resourceLocation;
398                }
399        }
400        
401        public static String getCalendarDateInIsoTimeFormat(Calendar calendar) {
402                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", new Locale("en", "US"));//TODO Move out
403                format.setTimeZone(TimeZone.getTimeZone("GMT"));
404            return format.format(calendar.getTime());
405        }
406        
407        public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) {
408                Map<String, String> parameters = new HashMap<String, String>();
409                parameters.put(httpParameterName, httpParameterValue);
410                return appendHttpParameters(basePath, parameters);
411        }
412        
413        public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) {
414        try {
415                Set<String> httpParameterNames = parameters.keySet();
416                String query = basePath.getQuery();
417                
418                for(String httpParameterName : httpParameterNames) {
419                        if(query != null) {
420                                query += "&";
421                        } else {
422                                query = "";
423                        }
424                        query += httpParameterName + "=" + Utilities.encodeUri(parameters.get(httpParameterName));
425                }
426        
427                return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment());
428        } catch(Exception e) {
429                throw new EFhirClientException("Error appending http parameter", e);
430        }
431    }
432        
433
434}