001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023 002package io.cui.tools.net; 003 004import static io.cui.tools.collect.CollectionLiterals.immutableList; 005import static io.cui.tools.string.MoreStrings.requireNotEmptyTrimmed; 006import static java.net.URLEncoder.encode; 007import static java.util.Objects.requireNonNull; 008import java.io.Serializable; 009import java.net.URLDecoder; 010import java.net.URLEncoder; 011import java.nio.charset.StandardCharsets; 012import java.util.ArrayList; 013import java.util.Collections; 014import java.util.Comparator; 015import java.util.HashMap; 016import java.util.List; 017import java.util.Map; 018import java.util.Map.Entry; 019import io.cui.tools.collect.CollectionBuilder; 020import io.cui.tools.collect.MoreCollections; 021import io.cui.tools.logging.CuiLogger; 022import io.cui.tools.string.Joiner; 023import io.cui.tools.string.MoreStrings; 024import io.cui.tools.string.Splitter; 025 026/** 027 * Simple wrapper around an Url Parameter Object. 028 * <p> 029 * Depending on the constructor arguments the attributes #getName() and 030 * #getValue() are implicitly encoded properly using 031 * {@link URLEncoder#encode(String, String)}. This is helpful for reliable 032 * handling of special characters. 033 * </p> 034 * 035 * @author Oliver Wolff 036 */ 037public class UrlParameter implements Serializable, Comparable<UrlParameter> { 038 private static final CuiLogger log = new CuiLogger(UrlParameter.class); 039 /** 040 * Shortcut constant for faces redirect parameter. 041 */ 042 public static final UrlParameter FACES_REDIRECT = new UrlParameter("faces-redirect", "true"); 043 /** 044 * Shortcut constant parameter for includeViewParams. 045 */ 046 public static final UrlParameter INCLUDE_VIEW_PARAMETER = new UrlParameter("includeViewParams", "true"); 047 private static final long serialVersionUID = 634175928228707534L; 048 /** 049 * The name of the parameter. 050 */ 051 private final String name; 052 /** 053 * The value of the parameter. 054 */ 055 private final String value; 056 057 /** 058 * Constructor. Name and value are implicitly encoded using UTF-8. 059 * 060 * @param name 061 * must not be null or empty 062 * @param value 063 * may be null. 064 */ 065 public UrlParameter(final String name, final String value) { 066 this(name, value, true); 067 } 068 069 /** 070 * Constructor. 071 * 072 * @param name 073 * must not be null or empty 074 * @param value 075 * value may be null. 076 * @param encode 077 * indicates whether to encode the parameter name and value as 078 * UTF-8 079 */ 080 public UrlParameter(final String name, final String value, final boolean encode) { 081 super(); 082 requireNotEmptyTrimmed(name, "Parameter name must not be empty"); 083 if (encode) { 084 this.name = encode(name, StandardCharsets.UTF_8); 085 if (MoreStrings.isEmpty(value)) { 086 this.value = null; 087 } else { 088 this.value = encode(value, StandardCharsets.UTF_8); 089 } 090 } else { 091 this.name = name; 092 this.value = value; 093 } 094 } 095 096 /** 097 * Returns a boolean indicating whether the {@link UrlParameter} is empty, 098 * saying has a null value 099 * 100 * @return boolean flag whether the {@link UrlParameter} is empty 101 */ 102 public boolean isEmpty() { 103 return null == value; 104 } 105 106 /** 107 * Creates a parameter String for a given number of {@link UrlParameter}. 108 * 109 * @param parameters 110 * to be appended, must not be null 111 * @return the concatenated ParameterString in the form 112 * "?parameter1Name=parameter1Value&parameter2Name=parameter2Value" 113 */ 114 public static String createParameterString(final UrlParameter... parameters) { 115 return createParameterString(false, parameters); 116 } 117 118 /** 119 * Create a String-representation of the URL-Parameter 120 * 121 * @param encode 122 * @param parameters 123 * @return the created parameter String 124 */ 125 public static String createParameterString(final boolean encode, final UrlParameter... parameters) { 126 final var builder = new StringBuilder(); 127 // First parameter to be treated specially. 128 if (null != parameters && parameters.length > 0 && null != parameters[0]) { 129 builder.append('?').append(parameters[0].createNameValueString(encode)); 130 if (parameters.length > 1) { 131 // The other parameters are appended with '&' 132 for (var i = 1; i < parameters.length; i++) { 133 builder.append('&').append(parameters[i].createNameValueString(encode)); 134 } 135 } 136 } 137 return builder.toString(); 138 } 139 140 // now need to throw exception 141 /** 142 * Convert a map of raw Url-parameter into a list of {@link UrlParameter} 143 * 144 * @param map 145 * containing the parameter extracted usually directly from 146 * servlet request. From the String[] solely the first element 147 * will be extracted. The others will be ignored. 148 * @param parameterFilter 149 * defines the parameter to be filtered. May be null or empty. 150 * @param encode 151 * indicates whether to encode the parameter name and value as 152 * UTF-8 153 * @return the found List of {@link UrlParameter} or empty list if the given 154 * map is null or empty. The List is always sorted by #getName() 155 */ 156 @SuppressWarnings("squid:S1166") 157 public static final List<UrlParameter> getUrlParameterFromMap(final Map<String, List<String>> map, final ParameterFilter parameterFilter, final boolean encode) { 158 if (MoreCollections.isEmpty(map)) { 159 return Collections.emptyList(); 160 } 161 final List<UrlParameter> extracted = new ArrayList<>(); 162 for (final Entry<String, List<String>> entry : map.entrySet()) { 163 String value = null; 164 if (!MoreCollections.isEmpty(entry.getValue())) { 165 value = entry.getValue().get(0); 166 } 167 final var key = entry.getKey(); 168 if (null == parameterFilter || !parameterFilter.isExcluded(key)) { 169 try { 170 extracted.add(new UrlParameter(key, value, encode)); 171 } catch (final IllegalArgumentException e) { 172 log.debug("Unable to read url parameter due to missing parameter name", e.getMessage()); 173 } 174 } 175 } 176 extracted.sort(Comparator.comparing(UrlParameter::getName)); 177 return extracted; 178 } 179 180 /** 181 * Filters the given list of {@link UrlParameter} 182 * 183 * @param toBeFiltered 184 * may be null or empty 185 * @param parameterFilter 186 * used for filtering, may be null 187 * @return the filtered parameter list or empty List if toBeFiltered is null 188 * or empty. 189 */ 190 public static List<UrlParameter> filterParameter(final List<UrlParameter> toBeFiltered, final ParameterFilter parameterFilter) { 191 if (toBeFiltered == null || toBeFiltered.isEmpty()) { 192 return Collections.emptyList(); 193 } 194 final var filtered = new ArrayList<UrlParameter>(); 195 for (final UrlParameter parameter : toBeFiltered) { 196 final var key = parameter.getName(); 197 if (null == parameterFilter || !parameterFilter.isExcluded(key)) { 198 filtered.add(parameter); 199 } 200 } 201 return filtered; 202 } 203 204 /** 205 * Create a parameterMap for a given list of {@link UrlParameter} 206 * 207 * @param urlParameters 208 * may be null or empty 209 * @return parameter Map, may be empty if urlParameters is empty. The list 210 * of String will solely contain one element. 211 */ 212 public static final Map<String, List<String>> createParameterMap(final List<UrlParameter> urlParameters) { 213 final Map<String, List<String>> result = new HashMap<>(); 214 if (null != urlParameters && !urlParameters.isEmpty()) { 215 for (final UrlParameter urlParameter : urlParameters) { 216 result.put(urlParameter.getName(), immutableList(urlParameter.getValue())); 217 } 218 } 219 return result; 220 } 221 222 /** 223 * Helper class that create a list of {@link UrlParameter} from a given query-String 224 * 225 * @param queryString if it is null or empty or solely consists of an "?" an empty {@link List} 226 * @return if queryString is null or empty or solely consists of an "?" an empty {@link List} 227 * will be returned. An immutable {@link List} of {@link UrlParameter} otherwise 228 */ 229 public static List<UrlParameter> fromQueryString(String queryString) { 230 log.trace("Parsing Query String %s", queryString); 231 if (MoreStrings.isEmpty(queryString)) { 232 return Collections.emptyList(); 233 } 234 var cleaned = queryString.trim(); 235 if (cleaned.startsWith("?")) { 236 cleaned = cleaned.substring(1); 237 } 238 if (MoreStrings.isEmpty(cleaned)) { 239 log.debug("Given String solely consists of \'?\' symbol, ignoring"); 240 return Collections.emptyList(); 241 } 242 var elements = Splitter.on("&").omitEmptyStrings().splitToList(cleaned); 243 var builder = new CollectionBuilder<UrlParameter>(); 244 for (String element : elements) { 245 if (element.contains("=")) { 246 var splitted = Splitter.on("=").omitEmptyStrings().splitToList(element); 247 switch (splitted.size()) { 248 case 0: 249 log.debug("Unable to parse queryString \'%s\' correctly, unable to extract key-value-pair for element \'%s\'", queryString, element); 250 break; 251 case 1: 252 builder.add(createDecoded(splitted.get(0), null)); 253 break; 254 case 2: 255 builder.add(createDecoded(splitted.get(0), splitted.get(1))); 256 break; 257 default: 258 log.debug("Unable to parse queryString \'%s\' correctly, multiple \'=\' symbols found at unexpected locations", queryString); 259 break; 260 } 261 } else { 262 builder.add(createDecoded(element, null)); 263 } 264 } 265 return builder.toImmutableList(); 266 } 267 268 private static UrlParameter createDecoded(final String name, final String value) { 269 requireNonNull(name); 270 String decodedKey; 271 decodedKey = URLDecoder.decode(name, StandardCharsets.UTF_8); 272 String decodedValue = null; 273 if (null != value) { 274 decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8); 275 } 276 return new UrlParameter(decodedKey, decodedValue, false); 277 } 278 279 /** 280 * Create a String representation of a name value pair, saying name=value 281 * 282 * @return String representation of a name value pair, saying name=value 283 */ 284 public String createNameValueString() { 285 return createNameValueString(false); 286 } 287 288 /** 289 * @param encode 290 * flag indicate if the result need to be encoded 291 * @return string representation of name + vale 292 */ 293 public String createNameValueString(final boolean encode) { 294 if (encode) { 295 return new UrlParameter(name, value).createNameValueString(); 296 } 297 return Joiner.on('=').useForNull("").join(name, value); 298 } 299 300 @Override 301 public int compareTo(final UrlParameter compareTo) { 302 return getName().compareTo(compareTo.getName()); 303 } 304 305 @java.lang.Override 306 @java.lang.SuppressWarnings("all") 307 @lombok.Generated 308 public boolean equals(final java.lang.Object o) { 309 if (o == this) return true; 310 if (!(o instanceof UrlParameter)) return false; 311 final UrlParameter other = (UrlParameter) o; 312 if (!other.canEqual((java.lang.Object) this)) return false; 313 final java.lang.Object this$name = this.getName(); 314 final java.lang.Object other$name = other.getName(); 315 if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; 316 final java.lang.Object this$value = this.getValue(); 317 final java.lang.Object other$value = other.getValue(); 318 if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false; 319 return true; 320 } 321 322 @java.lang.SuppressWarnings("all") 323 @lombok.Generated 324 protected boolean canEqual(final java.lang.Object other) { 325 return other instanceof UrlParameter; 326 } 327 328 @java.lang.Override 329 @java.lang.SuppressWarnings("all") 330 @lombok.Generated 331 public int hashCode() { 332 final int PRIME = 59; 333 int result = 1; 334 final java.lang.Object $name = this.getName(); 335 result = result * PRIME + ($name == null ? 43 : $name.hashCode()); 336 final java.lang.Object $value = this.getValue(); 337 result = result * PRIME + ($value == null ? 43 : $value.hashCode()); 338 return result; 339 } 340 341 @java.lang.Override 342 @java.lang.SuppressWarnings("all") 343 @lombok.Generated 344 public java.lang.String toString() { 345 return "UrlParameter(name=" + this.getName() + ", value=" + this.getValue() + ")"; 346 } 347 348 /** 349 * The name of the parameter. 350 */ 351 @java.lang.SuppressWarnings("all") 352 @lombok.Generated 353 public String getName() { 354 return this.name; 355 } 356 357 /** 358 * The value of the parameter. 359 */ 360 @java.lang.SuppressWarnings("all") 361 @lombok.Generated 362 public String getValue() { 363 return this.value; 364 } 365}