Skip to content

Commit

Permalink
Merge pull request #2161 from IBM/johntimm-main
Browse files Browse the repository at this point in the history
Issue #2092, Issue #1733, Issue #1980 - miscellaneous updates
  • Loading branch information
JohnTimm authored Mar 29, 2021
2 parents dcca3ab + 9bfb3c5 commit 67e3e1f
Show file tree
Hide file tree
Showing 9 changed files with 577 additions and 180 deletions.
257 changes: 257 additions & 0 deletions fhir-core/src/main/java/com/ibm/fhir/core/util/URLSupport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
/*
* (C) Copyright IBM Corp. 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.core.util;

import static com.ibm.fhir.core.util.LRUCache.createLRUCache;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* A utility class for working with URLs
*/
public class URLSupport {
private static final Map<String, URL> URL_CACHE = createLRUCache(128);

private URLSupport() { }

/**
* URL decode the input string
*
* @param s
* the string to URL decode
* @return
* the URL decoded string
*/
public static String decode(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

/**
* Get the first value of the list for the specified key from the provided multivalued map
*
* @param map
* the multivalued map
* @param key
* the key
* @return
* the first value of the list for the specified key from the provided multivalued map or null if not exists
*/
public static String getFirst(Map<String, List<String>> map, String key) {
List<String> values = map.get(key);
return (values != null && !values.isEmpty()) ? values.get(0) : null;
}

/**
* Get the path part of the provided URL
*
* @param url
* the url
* @return
* the path part or empty if not exists
* @see
* URL#getPath()
*/
public static String getPath(String url) {
return getURL(url).getPath();
}

/**
* Get a list containing the path segments from the provided URL
*
* <p>The path segments are URL decoded
*
* @param url
* the url
* @return
* a list containing the path segments from the provided URL
*/
public static List<String> getPathSegments(String url) {
return getPathSegments(url, true);
}

/**
* Get a list containing the path segments from the provided URL
*
* <p>The path segments are URL decoded according to the specified parameter
*
* @param url
* the url
* @param decode
* indicates whether to decode the path segments
* @return
* a list containing the path segments from the provided URL
*/
public static List<String> getPathSegments(String url, boolean decode) {
return parsePath(getPath(url), decode);
}

/**
* Get the query part of the provided URL
*
* @param url
* the URL
* @return
* the query part of the provided URL or empty if not exists
* @see
* URL#getQuery()
*/
public static String getQuery(String url) {
return getURL(url).getQuery();
}

/**
* Get a multivalued map containing the query parameters for the provided URL
*
* <p>The keys and values of the multivalued map are URL decoded
*
* @param url
* the URL
* @return
* a multivalued map containing the query parameters for the provided URL
*/
public static Map<String, List<String>> getQueryParameters(String url) {
return getQueryParameters(url, true);
}

/**
* Get a multivalued map containing the query parameters for the provided URL
*
* <p>The keys and values of the multivalued map are URL decoded according the specified parameter
*
* @param url
* the URL
* @param decode
* indicates whether to decode the keys and values of the multivalued map should be decoded
* @return
* a multivalued map containing the query parameters for the provided URL
*/
public static Map<String, List<String>> getQueryParameters(String url, boolean decode) {
return parseQuery(getQuery(url), decode);
}

/**
* Get a {@link URL} instance that represents the specified parameter
*
* @param url
* the url
* @return
* a {@link URL} instance that represents the specified parameter
* @see
* URL
*/
public static URL getURL(String url) {
return URL_CACHE.computeIfAbsent(url, k -> computeURL(url));
}

/**
* Parse the provided path part into a List of path segments
*
* <p>The path segments are URL decoded
*
* @param path
* the path part
* @return
* a list of path segments
*/
public static List<String> parsePath(String path) {
return parsePath(path, true);
}

/**
* Parse the provided path part into a list of path segments
*
* <p>The path segments are decoded according to the specified parameter
*
* @param path
* the path part
* @param decode
* indicates whether the path segments should be URL decoded
* @return
* a list of path segments
*/
public static List<String> parsePath(String path, boolean decode) {
return Arrays.stream(path.split("/"))
.skip(1)
.map(s -> decode ? decode(s) : s)
.collect(Collectors.toList());
}

/**
* Parse the provided query part into a multivalued map of query parameters
*
* <p>The keys and values of the multivalued map are URL decoded
*
* @param query
* the query part
* @return
* a multivalued map containing the query parameters for the provided URL
*
*/
public static Map<String, List<String>> parseQuery(String query) {
return parseQuery(query, true);
}

/**
* Parse the provided query part into a multivalued map of query parameters
*
* <p>The keys and values of the multivalued map are URL decoded according to the specified parameter
*
* @param query
* the query part
* @param decode
* indicates whether to decode the keys and values of the multivalued map should be decoded
* @return
* a multivalued map containing the query parameters for the provided URL
*
*/
public static Map<String, List<String>> parseQuery(String query, boolean decode) {
if (query == null || query.isEmpty()) {
return Collections.emptyMap();
}
return Arrays.stream(query.split("&"))
.map(pair -> Arrays.asList(pair.split("=", 2)))
.collect(Collectors.collectingAndThen(
Collectors.toMap(
// key mapping function
pair -> decode ? decode(pair.get(0)) : pair.get(0),
// value mapping function
pair -> (pair.size() > 1) ? Collections.unmodifiableList(Arrays.stream(pair.get(1).split(","))
.map(s -> decode ? decode(s) : s)
.collect(Collectors.toList())) : Collections.<String>emptyList(),
// merge function
(u, v) -> {
List<String> merged = new ArrayList<>(u);
merged.addAll(v);
return Collections.unmodifiableList(merged);
},
// map supplier
LinkedHashMap::new),
Collections::unmodifiableMap));
}

private static URL computeURL(String url) {
try {
return new URL(url);
} catch (MalformedURLException e) {
throw new RuntimeException (e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* (C) Copyright IBM Corp. 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.core.util.test;

import static com.ibm.fhir.core.util.URLSupport.getFirst;
import static com.ibm.fhir.core.util.URLSupport.getPathSegments;
import static com.ibm.fhir.core.util.URLSupport.getQueryParameters;
import static com.ibm.fhir.core.util.URLSupport.parseQuery;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.testng.Assert;
import org.testng.annotations.Test;

public class URLSupportTest {
@Test
public void testGetQueryParameters() {
String url = "http://ibm.com/fhir/ValueSet/generalizes?system=http://ibm.com/fhir/CodeSystem/cs5&code=r";
Map<String, List<String>> queryParameters = getQueryParameters(url);
Assert.assertEquals(getFirst(queryParameters, "system"), "http://ibm.com/fhir/CodeSystem/cs5");
Assert.assertEquals(getFirst(queryParameters, "code"), "r");
}

@Test
public void testGetPathSegments() {
String url = "http://ibm.com/fhir/ValueSet/generalizes?system=http://ibm.com/fhir/CodeSystem/cs5&code=r";
List<String> actual = getPathSegments(url);
List<String> expected = Arrays.asList("fhir", "ValueSet", "generalizes");
Assert.assertEquals(actual, expected);
}

@Test
public void testParseQuery() throws Exception {
String query = "name1=value1%7Cvalue2%7Cvalue3";
Map<String, List<String>> queryParameters = parseQuery(query);
Assert.assertEquals(getFirst(queryParameters, "name1"), "value1|value2|value3");
queryParameters = parseQuery(query, false);
Assert.assertEquals(getFirst(queryParameters, "name1"), "value1%7Cvalue2%7Cvalue3");
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*
* (C) Copyright IBM Corp. 2020
* (C) Copyright IBM Corp. 2020, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.path.function;

import static com.ibm.fhir.core.util.URLSupport.parseQuery;
import static com.ibm.fhir.model.type.String.string;
import static com.ibm.fhir.model.util.ModelSupport.FHIR_STRING;
import static com.ibm.fhir.path.util.FHIRPathUtil.getElementNode;
Expand All @@ -17,13 +18,9 @@
import static com.ibm.fhir.path.util.FHIRPathUtil.isSingleton;
import static com.ibm.fhir.path.util.FHIRPathUtil.isStringValue;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand Down Expand Up @@ -123,7 +120,7 @@ protected com.ibm.fhir.model.type.String getDisplay(FHIRPathTree tree, FHIRPathE
protected Parameters getParameters(List<Collection<FHIRPathNode>> arguments) {
if (arguments.size() == getMaxArity()) {
String params = getString(arguments.get(arguments.size() - 1));
Map<String, List<String>> queryParameters = parse(params);
Map<String, List<String>> queryParameters = parseQuery(params);
return buildParameters(queryParameters);
}
return EMPTY_PARAMETERS;
Expand Down Expand Up @@ -183,34 +180,4 @@ private List<Parameter> parameter(
.build())
.collect(Collectors.toList());
}

private Map<String, List<String>> parse(String params) {
return Arrays.stream(params.split("&"))
.map(pair -> Arrays.asList(pair.split("=", 2)))
.collect(Collectors.collectingAndThen(
Collectors.toMap(
// key mapping function
pair -> decode(pair.get(0)),
// value mapping function
pair -> Collections.unmodifiableList(Arrays.stream(pair.get(1).split(","))
.map(s -> decode(s))
.collect(Collectors.toList())),
// merge function
(u, v) -> {
List<String> merged = new ArrayList<>(u);
merged.addAll(v);
return Collections.unmodifiableList(merged);
},
// map supplier
LinkedHashMap::new),
Collections::unmodifiableMap));
}

private String decode(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,9 @@ public int compareTo(Version version) {
}

public static FHIRRegistryResource from(Resource resource) {
Objects.requireNonNull(resource, "resource");
if (resource == null) {
return null;
}

Class<? extends Resource> resourceType = resource.getClass();
requireDefinitionalResourceType(resourceType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ public Set<Concept> getConcepts(CodeSystem codeSystem, List<Filter> filters) {
first = false;
}

if (filters.isEmpty()) {
g = g.hasLabel("Concept");
}

g = g.timeLimit(timeLimit);
TimeLimitStep<?> timeLimitStep = getTimeLimitStep(g);

Expand Down
Loading

0 comments on commit 67e3e1f

Please sign in to comment.