001package org.hl7.fhir.dstu3.utils.client;
002
003
004
005
006
007import java.net.URI;
008import java.net.URISyntaxException;
009import java.text.SimpleDateFormat;
010import java.util.Calendar;
011import java.util.Date;
012import java.util.HashMap;
013import java.util.Locale;
014import java.util.Map;
015import java.util.Set;
016import java.util.TimeZone;
017import java.util.regex.Matcher;
018import java.util.regex.Pattern;
019
020/*
021  Copyright (c) 2011+, HL7, Inc.
022  All rights reserved.
023  
024  Redistribution and use in source and binary forms, with or without modification, 
025  are permitted provided that the following conditions are met:
026  
027   * Redistributions of source code must retain the above copyright notice, this 
028     list of conditions and the following disclaimer.
029   * Redistributions in binary form must reproduce the above copyright notice, 
030     this list of conditions and the following disclaimer in the documentation 
031     and/or other materials provided with the distribution.
032   * Neither the name of HL7 nor the names of its contributors may be used to 
033     endorse or promote products derived from this software without specific 
034     prior written permission.
035  
036  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
037  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
038  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
039  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
040  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
041  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
042  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
043  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
044  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
045  POSSIBILITY OF SUCH DAMAGE.
046  
047*/
048
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/**
059 * Helper class to manage FHIR Resource URIs
060 * 
061 * @author Claude Nanjo
062 *
063 */
064public class ResourceAddress {
065        
066        public static final String REGEX_ID_WITH_HISTORY = "(.*)(/)([a-zA-Z0-9]*)(/)([a-z0-9\\-\\.]{1,64})(/_history/)([a-z0-9\\-\\.]{1,64})$";
067        
068        private URI baseServiceUri;
069        
070        public ResourceAddress(String endpointPath) throws URISyntaxException {//TODO Revisit this exception
071                this.baseServiceUri = ResourceAddress.buildAbsoluteURI(endpointPath);
072        }
073        
074        public ResourceAddress(URI baseServiceUri) {
075                this.baseServiceUri = baseServiceUri;
076        }
077        
078        public URI getBaseServiceUri() {
079                return this.baseServiceUri;
080        }
081        
082        public <T extends Resource> URI resolveOperationURLFromClass(Class<T> resourceClass, String name, String parameters) {
083                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+name+"?"+ parameters);
084        }
085        
086        public <T extends Resource> URI resolveSearchUri(Class<T> resourceClass, Map<String,String> parameters) {
087                return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"_search"), parameters);
088        }
089        
090  private <T extends Resource> String nameForClassWithSlash(Class<T> resourceClass) {
091    String n = nameForClass(resourceClass);
092    return n == null ? "" : n +"/";
093        }
094        
095  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName) {
096    return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"/"+opName);
097  }
098  
099  public <T extends Resource> URI resolveOperationUri(Class<T> resourceClass, String opName, Map<String,String> parameters) {
100    return appendHttpParameters(baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$"+opName), parameters);
101  }
102  
103        public <T extends Resource> URI resolveValidateUri(Class<T> resourceClass, String id) {
104                return baseServiceUri.resolve(nameForClassWithSlash(resourceClass) +"$validate/"+id);
105        }
106        
107        public <T extends Resource> URI resolveGetUriFromResourceClass(Class<T> resourceClass) {
108                return baseServiceUri.resolve(nameForClass(resourceClass));
109        }
110        
111        public <T extends Resource> URI resolveGetUriFromResourceClassAndId(Class<T> resourceClass, String id) {
112                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id);
113        }
114        
115        public <T extends Resource> URI resolveGetUriFromResourceClassAndIdAndVersion(Class<T> resourceClass, String id, String version) {
116                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version);
117        }
118        
119  public <T extends Resource> URI resolveGetUriFromResourceClassAndCanonical(Class<T> resourceClass, String canonicalUrl) {
120    if (canonicalUrl.contains("|"))
121      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl.substring(0, canonicalUrl.indexOf("|"))+"&version="+canonicalUrl.substring(canonicalUrl.indexOf("|")+1));      
122    else
123      return baseServiceUri.resolve(nameForClass(resourceClass)+"?url="+canonicalUrl);
124  }
125  
126        public URI resolveGetHistoryForAllResources(int count) {
127                if(count > 0) {
128                        return appendHttpParameter(baseServiceUri.resolve("_history"), "_count", ""+count);
129                } else {
130                        return baseServiceUri.resolve("_history");
131                }
132}
133        
134        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, int count) {
135                return resolveGetHistoryUriForResourceId(resourceClass, id, null, count);
136        }
137        
138        protected <T extends Resource> URI resolveGetHistoryUriForResourceId(Class<T> resourceClass, String id, Object since, int count) {
139                Map<String,String>  parameters = getHistoryParameters(since, count);
140                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_history"), parameters);
141        }
142        
143        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, int count) {
144                Map<String,String>  parameters = getHistoryParameters(null, count);
145                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
146        }
147        
148        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Object since, int count) {
149                Map<String,String>  parameters = getHistoryParameters(since, count);
150                return appendHttpParameters(baseServiceUri.resolve(nameForClass(resourceClass) + "/_history"), parameters);
151        }
152        
153        public URI resolveGetHistoryForAllResources(Calendar since, int count) {
154                Map<String,String>  parameters = getHistoryParameters(since, count);
155                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
156        }
157        
158        public URI resolveGetHistoryForAllResources(Date since, int count) {
159                Map<String,String>  parameters = getHistoryParameters(since, count);
160                return appendHttpParameters(baseServiceUri.resolve("_history"), parameters);
161        }
162        
163        public Map<String,String> getHistoryParameters(Object since, int count) {
164                Map<String,String>  parameters = new HashMap<String,String>();
165                if (since != null) {
166                        parameters.put("_since", since.toString());
167                }
168                if(count > 0) {
169                        parameters.put("_count", ""+count);
170                }
171                return parameters;
172        }
173        
174        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Calendar since, int count) {
175                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
176        }
177        
178        public <T extends Resource> URI resolveGetHistoryForResourceId(Class<T> resourceClass, String id, Date since, int count) {
179                return resolveGetHistoryUriForResourceId(resourceClass, id, since, count);
180        }
181        
182        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Calendar since, int count) {
183                return resolveGetHistoryForResourceType(resourceClass, getCalendarDateInIsoTimeFormat(since), count);
184        }
185        
186        public <T extends Resource> URI resolveGetHistoryForResourceType(Class<T> resourceClass, Date since, int count) {
187                return resolveGetHistoryForResourceType(resourceClass, since.toString(), count);
188        }
189        
190        public <T extends Resource> URI resolveGetAllTags() {
191                return baseServiceUri.resolve("_tags");
192        }
193        
194        public <T extends Resource> URI resolveGetAllTagsForResourceType(Class<T> resourceClass) {
195                return baseServiceUri.resolve(nameForClass(resourceClass) + "/_tags");
196        }
197        
198        public <T extends Resource> URI resolveGetTagsForReference(Class<T> resourceClass, String id) {
199                return baseServiceUri.resolve(nameForClass(resourceClass) + "/" + id + "/_tags");
200        }
201        
202        public <T extends Resource> URI resolveGetTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
203                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags");
204        }
205        
206        public <T extends Resource> URI resolveDeleteTagsForResourceVersion(Class<T> resourceClass, String id, String version) {
207                return baseServiceUri.resolve(nameForClass(resourceClass) +"/"+id+"/_history/"+version + "/_tags/_delete");
208        }
209        
210
211        public <T extends Resource> String nameForClass(Class<T> resourceClass) {
212          if (resourceClass == null)
213            return null;
214                String res = resourceClass.getSimpleName();
215                if (res.equals("List_"))
216                        return "List";
217                else
218                        return res;
219        }
220        
221  public URI resolveMetadataUri(boolean quick) {
222    return baseServiceUri.resolve(quick ? "metadata?_summary=true" : "metadata");
223  }
224  
225  public URI resolveMetadataTxCaps() {
226    return baseServiceUri.resolve("metadata?mode=terminology");
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        public static ResourceVersionedIdentifier parseCreateLocation(String locationResponseHeader) {
234                Pattern pattern = Pattern.compile(REGEX_ID_WITH_HISTORY);
235                Matcher matcher = pattern.matcher(locationResponseHeader);
236                ResourceVersionedIdentifier parsedHeader = null;
237                if(matcher.matches()){
238                        String serviceRoot = matcher.group(1);
239                        String resourceType = matcher.group(3);
240                        String id = matcher.group(5);
241                        String version = matcher.group(7);
242                        parsedHeader = new ResourceVersionedIdentifier(serviceRoot, resourceType, id, version);
243                }
244                return parsedHeader;
245        }
246        
247        public static URI buildAbsoluteURI(String absoluteURI) {
248                
249                if(StringUtils.isBlank(absoluteURI)) {
250                        throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
251                } 
252                
253                String endpoint = appendForwardSlashToPath(absoluteURI);
254
255                return buildEndpointUriFromString(endpoint);
256        }
257        
258        public static String appendForwardSlashToPath(String path) {
259                if(path.lastIndexOf('/') != path.length() - 1) {
260                        path += "/";
261                }
262                return path;
263        }
264        
265        public static URI buildEndpointUriFromString(String endpointPath) {
266                URI uri = null; 
267                try {
268                        URIBuilder uriBuilder = new URIBuilder(endpointPath);
269                        uri = uriBuilder.build();
270                        String scheme = uri.getScheme();
271                        String host = uri.getHost();
272                        if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
273                                throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri);
274                        }
275                        if(StringUtils.isBlank(host)) {
276                                throw new EFhirClientException("host cannot be blank: " + uri);
277                        }
278                } catch(URISyntaxException e) {
279                        throw new EFhirClientException("Invalid URI", e);
280                }
281                return uri;
282        }
283        
284        public static URI appendQueryStringToUri(URI uri, String parameterName, String parameterValue) {
285                URI modifiedUri = null;
286                try {
287                        URIBuilder uriBuilder = new URIBuilder(uri);
288                        uriBuilder.setQuery(parameterName + "=" + parameterValue);
289                        modifiedUri = uriBuilder.build();
290                } catch(Exception e) {
291                        throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
292                }
293                return modifiedUri;
294        }
295        
296        public static String buildRelativePathFromResourceType(ResourceType resourceType) {
297                //return resourceType.toString().toLowerCase()+"/";
298                return resourceType.toString() + "/";
299        }
300        
301        public static String buildRelativePathFromResourceType(ResourceType resourceType, String id) {
302                return buildRelativePathFromResourceType(resourceType)+ "@" + id;
303        }
304        
305        public static String buildRelativePathFromReference(Resource resource) {
306                return buildRelativePathFromResourceType(resource.getResourceType());
307        }
308        
309        public static String buildRelativePathFromReference(Resource resource, String id) {
310                return buildRelativePathFromResourceType(resource.getResourceType(), id);
311        }
312        
313        public static class ResourceVersionedIdentifier {
314                
315                private String serviceRoot;
316                private String resourceType;
317                private String id;
318                private String version;
319                private URI resourceLocation;
320                
321                public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version, URI resourceLocation) {
322                        this.serviceRoot = serviceRoot;
323                        this.resourceType = resourceType;
324                        this.id = id;
325                        this.version = version;
326                        this.resourceLocation = resourceLocation;
327                }
328                
329                public ResourceVersionedIdentifier(String resourceType, String id, String version, URI resourceLocation) {
330                        this(null, resourceType, id, version, resourceLocation);
331                }
332                
333                public ResourceVersionedIdentifier(String serviceRoot, String resourceType, String id, String version) {
334                        this(serviceRoot, resourceType, id, version, null);
335                }
336                
337                public ResourceVersionedIdentifier(String resourceType, String id, String version) {
338                        this(null, resourceType, id, version, null);
339                }
340                
341                public ResourceVersionedIdentifier(String resourceType, String id) {
342                        this.id = id;
343                }
344                
345                public String getId() {
346                        return this.id;
347                }
348                
349                protected void setId(String id) {
350                        this.id = id;
351                }
352                
353                public String getVersionId() {
354                        return this.version;
355                }
356                
357                protected void setVersionId(String version) {
358                        this.version = version;
359                }
360                
361                public String getResourceType() {
362                        return resourceType;
363                }
364
365                public void setResourceType(String resourceType) {
366                        this.resourceType = resourceType;
367                }
368                
369                public String getServiceRoot() {
370                        return serviceRoot;
371                }
372
373                public void setServiceRoot(String serviceRoot) {
374                        this.serviceRoot = serviceRoot;
375                }
376                
377                public String getResourcePath() {
378                        return this.serviceRoot + "/" + this.resourceType + "/" + this.id;
379                }
380
381                public String getVersion() {
382                        return version;
383                }
384
385                public void setVersion(String version) {
386                        this.version = version;
387                }
388
389                public URI getResourceLocation() {
390                        return this.resourceLocation;
391                }
392                
393                public void setResourceLocation(URI resourceLocation) {
394                        this.resourceLocation = resourceLocation;
395                }
396        }
397        
398        public static String getCalendarDateInIsoTimeFormat(Calendar calendar) {
399                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", new Locale("en", "US"));//TODO Move out
400                format.setTimeZone(TimeZone.getTimeZone("GMT"));
401            return format.format(calendar.getTime());
402        }
403        
404        public static URI appendHttpParameter(URI basePath, String httpParameterName, String httpParameterValue) {
405                Map<String, String> parameters = new HashMap<String, String>();
406                parameters.put(httpParameterName, httpParameterValue);
407                return appendHttpParameters(basePath, parameters);
408        }
409        
410        public static URI appendHttpParameters(URI basePath, Map<String,String> parameters) {
411        try {
412                Set<String> httpParameterNames = parameters.keySet();
413                String query = basePath.getQuery();
414                
415                for(String httpParameterName : httpParameterNames) {
416                        if(query != null) {
417                                query += "&";
418                        } else {
419                                query = "";
420                        }
421                        query += httpParameterName + "=" + Utilities.encodeUri(parameters.get(httpParameterName));
422                }
423        
424                return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment());
425        } catch(Exception e) {
426                throw new EFhirClientException("Error appending http parameter", e);
427        }
428    }
429        
430}