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