Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2092, Issue #1733, Issue #1980 - miscellaneous updates #2161

Merged
merged 6 commits into from
Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
JohnTimm marked this conversation as resolved.
Show resolved Hide resolved
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