-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2161 from IBM/johntimm-main
- Loading branch information
Showing
9 changed files
with
577 additions
and
180 deletions.
There are no files selected for viewing
257 changes: 257 additions & 0 deletions
257
fhir-core/src/main/java/com/ibm/fhir/core/util/URLSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
fhir-core/src/test/java/com/ibm/fhir/core/util/test/URLSupportTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.