001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.http; 014 015import static org.apache.juneau.internal.StringUtils.*; 016 017import java.util.*; 018import java.util.concurrent.*; 019import java.util.function.*; 020import java.util.stream.*; 021 022import org.apache.http.*; 023import org.apache.juneau.*; 024import org.apache.juneau.httppart.*; 025import org.apache.juneau.internal.*; 026import org.apache.juneau.oapi.*; 027import org.apache.juneau.svl.*; 028import org.apache.juneau.urlencoding.*; 029 030/** 031 * Specifies a dynamic supplier of {@link NameValuePair} objects. 032 * 033 * This class is thread safe. 034 */ 035public class NameValuePairSupplier implements Iterable<NameValuePair> { 036 037 /** Represents no header supplier */ 038 public static final class Null extends NameValuePairSupplier {} 039 040 private final List<Iterable<NameValuePair>> pairs = new CopyOnWriteArrayList<>(); 041 042 private volatile VarResolver varResolver; 043 044 /** 045 * Convenience creator. 046 * 047 * @return A new {@link NameValuePairSupplier} object. 048 */ 049 public static NameValuePairSupplier create() { 050 return new NameValuePairSupplier(); 051 } 052 053 /** 054 * Creates an empty instance. 055 * 056 * @return A new empty instance. 057 */ 058 public static NameValuePairSupplier of() { 059 return new NameValuePairSupplier(); 060 } 061 062 /** 063 * Creates an instance initialized with the specified pairs. 064 * 065 * @param pairs The pairs to add to this list. 066 * @return A new instance. 067 */ 068 public static NameValuePairSupplier of(Collection<NameValuePair> pairs) { 069 return new NameValuePairSupplier().addAll(pairs); 070 } 071 072 /** 073 * Creates an instance initialized with the specified pairs. 074 * 075 * @param parameters 076 * Initial list of parameters. 077 * <br>Must be an even number of parameters representing key/value pairs. 078 * @throws RuntimeException If odd number of parameters were specified. 079 * @return A new instance. 080 */ 081 public static NameValuePairSupplier ofPairs(Object...parameters) { 082 NameValuePairSupplier s = NameValuePairSupplier.create(); 083 if (parameters.length % 2 != 0) 084 throw new BasicRuntimeException("Odd number of parameters passed into NameValuePairSupplier.ofPairs()"); 085 for (int i = 0; i < parameters.length; i+=2) 086 s.add(stringify(parameters[i]), parameters[i+1]); 087 return s; 088 } 089 090 /** 091 * Convenience creator. 092 * 093 * @param values 094 * The values to populate this supplier with. 095 * <br>Can be any of the following types: 096 * <ul> 097 * <li>{@link NameValuePair}. 098 * <li>{@link NameValuePairSupplier}. 099 * </ul> 100 * @return A new {@link NameValuePairSupplier} object. 101 */ 102 public static NameValuePairSupplier of(Object...values) { 103 NameValuePairSupplier s = NameValuePairSupplier.create(); 104 for (Object v : values) { 105 if (v instanceof NameValuePair) 106 s.add((NameValuePair)v); 107 else if (v instanceof NameValuePairSupplier) 108 s.add((NameValuePairSupplier)v); 109 else if (v != null) 110 throw new BasicRuntimeException("Invalid type passed to NameValuePairSupplier.of(): {0}", v.getClass().getName()); 111 } 112 return s; 113 } 114 115 /** 116 * Allows values to contain SVL variables. 117 * 118 * <p> 119 * Resolves variables in values when using the following methods: 120 * <ul> 121 * <li class='jm'>{@link #ofPairs(Object...) ofPairs(Object...)} 122 * <li class='jm'>{@link #add(String, Object) add(String,Object)} 123 * <li class='jm'>{@link #add(String, Supplier) add(String,Supplier<?>)} 124 * <li class='jm'>{@link #add(String, Object, HttpPartType, HttpPartSerializerSession, HttpPartSchema, boolean) add(String,Object,HttpPartType,HttpPartSerializerSession,HttpPartSchema,boolean)} 125 * </ul> 126 * 127 * <p> 128 * Uses {@link VarResolver#DEFAULT} to resolve variables. 129 * 130 * @return This object (for method chaining). 131 */ 132 public NameValuePairSupplier resolving() { 133 return resolving(VarResolver.DEFAULT); 134 } 135 136 /** 137 * Allows values to contain SVL variables. 138 * 139 * <p> 140 * Resolves variables in values when using the following methods: 141 * <ul> 142 * <li class='jm'>{@link #ofPairs(Object...) ofPairs(Object...)} 143 * <li class='jm'>{@link #add(String, Object) add(String,Object)} 144 * <li class='jm'>{@link #add(String, Supplier) add(String,Supplier<?>)} 145 * <li class='jm'>{@link #add(String, Object, HttpPartType, HttpPartSerializerSession, HttpPartSchema, boolean) add(String,Object,HttpPartType,HttpPartSerializerSession,HttpPartSchema,boolean)} 146 * </ul> 147 * 148 * @param varResolver The variable resolver to use for resolving variables. 149 * @return This object (for method chaining). 150 */ 151 public NameValuePairSupplier resolving(VarResolver varResolver) { 152 this.varResolver = varResolver; 153 return this; 154 } 155 156 /** 157 * Add a name-value pair to this supplier. 158 * 159 * @param h The name-value pair to add. <jk>null</jk> values are ignored. 160 * @return This object (for method chaining). 161 */ 162 public NameValuePairSupplier add(NameValuePair h) { 163 if (h != null) 164 pairs.add(Collections.singleton(h)); 165 return this; 166 } 167 168 /** 169 * Add a supplier to this supplier. 170 * 171 * @param h The supplier to add. <jk>null</jk> values are ignored. 172 * @return This object (for method chaining). 173 */ 174 public NameValuePairSupplier add(NameValuePairSupplier h) { 175 if (h != null) 176 pairs.add(h); 177 return this; 178 } 179 180 /** 181 * Adds all the specified name-value pairs to this supplier. 182 * 183 * @param pairs The pairs to add to this supplier. 184 * @return This object(for method chaining). 185 */ 186 private NameValuePairSupplier addAll(Collection<NameValuePair> pairs) { 187 this.pairs.addAll(pairs.stream().filter(x->x != null).map(x->Collections.singleton(x)).collect(Collectors.toList())); 188 return this; 189 } 190 191 //------------------------------------------------------------------------------------------------------------------ 192 // Appenders 193 //------------------------------------------------------------------------------------------------------------------ 194 195 /** 196 * Appends the specified name/value pair to the end of this list. 197 * 198 * <p> 199 * The pair is added as a {@link BasicNameValuePair}. 200 * 201 * @param name The pair name. 202 * @param value The pair value. 203 * @return This object (for method chaining). 204 */ 205 public NameValuePairSupplier add(String name, Object value) { 206 return add(new BasicNameValuePair(name, resolver(value))); 207 } 208 209 /** 210 * Appends the specified name/value pair to the end of this list using a value supplier. 211 * 212 * <p> 213 * The pair is added as a {@link BasicNameValuePair}. 214 * 215 * <p> 216 * Value is re-evaluated on each call to {@link BasicNameValuePair#getValue()}. 217 * 218 * @param name The pair name. 219 * @param value The pair value supplier. 220 * @return This object (for method chaining). 221 */ 222 public NameValuePairSupplier add(String name, Supplier<?> value) { 223 return add(new BasicNameValuePair(name, resolver(value))); 224 } 225 226 /** 227 * Appends the specified name/value pair to the end of this list. 228 * 229 * <p> 230 * The value is converted to UON notation using the {@link UrlEncodingSerializer} defined on the client. 231 * 232 * @param name The pair name. 233 * @param value The pair value. 234 * @param partType The HTTP part type. 235 * @param serializer 236 * The serializer to use for serializing the value to a string value. 237 * @param schema 238 * The schema object that defines the format of the output. 239 * <br>If <jk>null</jk>, defaults to the schema defined on the parser. 240 * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}. 241 * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}). 242 * @param skipIfEmpty If value is a blank string, the value should return as <jk>null</jk>. 243 * @return This object (for method chaining). 244 */ 245 public NameValuePairSupplier add(String name, Object value, HttpPartType partType, HttpPartSerializerSession serializer, HttpPartSchema schema, boolean skipIfEmpty) { 246 return add(new SerializedNameValuePair(name, resolver(value), partType, serializer, schema, skipIfEmpty)); 247 } 248 249 /** 250 * Returns this list as a URL-encoded custom query. 251 */ 252 @Override /* Object */ 253 public String toString() { 254 StringBuilder sb = new StringBuilder(); 255 for (NameValuePair p : this) { 256 String v = p.getValue(); 257 if (v != null) { 258 if (sb.length() > 0) 259 sb.append("&"); 260 sb.append(urlEncode(p.getName())).append('=').append(urlEncode(p.getValue())); 261 } 262 } 263 return sb.toString(); 264 } 265 266 @Override 267 public Iterator<NameValuePair> iterator() { 268 return CollectionUtils.iterator(pairs); 269 } 270 271 /** 272 * Returns these pairs as an array. 273 * 274 * @return These pairs as an array. 275 */ 276 public NameValuePair[] toArray() { 277 ArrayList<NameValuePair> l = new ArrayList<>(); 278 for (NameValuePair p : this) 279 l.add(p); 280 return l.toArray(new NameValuePair[l.size()]); 281 } 282 283 /** 284 * Returns these pairs as an array. 285 * 286 * @param array The array to copy in to. 287 * @return These pairs as an array. 288 */ 289 public <T extends NameValuePair> T[] toArray(T[] array) { 290 ArrayList<NameValuePair> l = new ArrayList<>(); 291 for (NameValuePair p : this) 292 l.add(p); 293 return l.toArray(array); 294 } 295 296 private Supplier<Object> resolver(Object input) { 297 return ()->(varResolver == null ? unwrap(input) : varResolver.resolve(stringify(unwrap(input)))); 298 } 299 300 private Object unwrap(Object o) { 301 while (o instanceof Supplier) 302 o = ((Supplier<?>)o).get(); 303 return o; 304 } 305}