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