/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Kevin Smith, Boundless, 2017
*/
package org.geowebcache.filter.parameters;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.codec.digest.DigestUtils;
public class ParametersUtils {
/**
*
* This should be treated as an opaque Identifier and should not be parsed, it is used to
* to maintain compatibility with old caches. For any other uses, {@link getKVP} is preferred
* as it uses safe escaping of values.
*
* @param parameters
* @return
*/
public static String getLegacyParametersKvp(Map<String, String> parameters) {
StringBuilder sb = new StringBuilder();
SortedMap<String, String> sorted = new TreeMap<String, String>(parameters);
for (Map.Entry<String, String> e : sorted.entrySet()) {
if(sb.length() == 0) {
sb.append("?");
} else {
sb.append("&");
}
sb.append(e.getKey()).append('=').append(e.getValue());
}
String paramtersKvp = sb.toString();
return paramtersKvp;
}
private static String encUTF8(String s) {
try {
// This would be much nicer if URLEncoder.encode took a charset object instead of a string
return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Standard encoding UTF-8 is missing");
}
}
private static String decUTF8(String s) {
try {
// This would be much nicer if URLDecoder.decode took a charset object instead of a string
return URLDecoder.decode(s, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Standard encoding UTF-8 is missing");
}
}
/**
* Create a comparator that compares the results of a function applied to both its arguments.
* @param derivation A function taking a T and returning a Comparable
* @return A comparator that compares the result of derivation applied to both arguments.
*/
static <T, U extends Comparable<U>> Comparator<T> derivedComparator(Function<T, U> derivation) {
return (t1,t2)->derivation.apply(t1).compareTo(derivation.apply(t2));
}
/**
* Turns the parameter list into a sorted KVP string
*
* @param parameters
* @return
*/
public static String getKvp(Map<String, String> parameters) {
return parameters.entrySet().stream()
.sorted(derivedComparator(Entry::getKey))
.map(e->String.join("=", encUTF8(e.getKey()), encUTF8(e.getValue())))
.collect(Collectors.joining("&"));
}
/**
* Turns the a sorted KVP string into a parameter map.
*
* This should only be used for parsing strings created by {@link getSafeParametersKvp} not for
* parsing raw query strings
*
* @param parameters
* @return
*/
public static Map<String, String> getMap(String kvp) {
return Arrays.stream(kvp.split("&"))
.filter(((Predicate<String>)String::isEmpty).negate())
.map(pair->pair.split("=", 2))
.collect(Collectors.toMap(p->decUTF8(p[0]), p->decUTF8(p[1])));
}
/**
* Returns the parameters identifier for the given parameters map
* @param parameters
* @return
*/
public static String getId(Map<String, String> parameters) {
if(parameters == null || parameters.size() == 0) {
return null;
}
String parametersKvp = getLegacyParametersKvp(parameters);
return ParametersUtils.buildKey(parametersKvp);
}
public static String buildKey(String parametersKvp) {
return DigestUtils.sha1Hex(parametersKvp);
}
}