1 /*
2 * Copyright (c) 2011-2024 PrimeFaces Extensions
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22 package org.primefaces.extensions.util;
23
24 import java.io.UnsupportedEncodingException;
25 import java.net.URLEncoder;
26
27 import javax.faces.context.FacesContext;
28 import javax.servlet.http.HttpServletRequest;
29
30 import org.primefaces.extensions.converter.JsonConverter;
31 import org.primefaces.extensions.converter.JsonExposeAwareConverter;
32
33 /**
34 * Builder for request parameters.
35 *
36 * @author Oleg Varaksin
37 * @since 1.1.0
38 */
39 public class RequestParameterBuilder {
40
41 private StringBuilder buffer;
42 private final String originalUrl;
43 private final JsonConverter jsonConverter;
44 private String encoding;
45 private boolean added;
46
47 /**
48 * Creates a builder instance. This constructor is useful when we only use encode() and encodeJson() methods.
49 */
50 public RequestParameterBuilder() {
51 this(null);
52 }
53
54 /**
55 * Creates a builder instance wihout URL or with the current request URL.
56 *
57 * @param useCurrentRequest boolean flag if the current request URL should be used or not
58 */
59 public RequestParameterBuilder(final boolean useCurrentRequest) {
60 this(useCurrentRequest
61 ? ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext()
62 .getRequest()).getRequestURL()
63 .toString()
64 : null);
65 }
66
67 /**
68 * Creates a builder instance by the given URL.
69 *
70 * @param url URL
71 */
72 public RequestParameterBuilder(final String url) {
73 this(url, null);
74 }
75
76 /**
77 * Creates a builder instance by the given URL.
78 *
79 * @param url URL
80 * @param jsonConverter specific JsonConverter. It should extends {@link JsonConverter} from PrimeFaces Extensions. If null, the default implementation
81 * {@link JsonExposeAwareConverter} is used.
82 */
83 public RequestParameterBuilder(final String url, final JsonConverter jsonConverter) {
84 buffer = new StringBuilder(url != null ? url : org.primefaces.util.Constants.EMPTY_STRING);
85 originalUrl = url != null ? url : org.primefaces.util.Constants.EMPTY_STRING;
86
87 this.jsonConverter = jsonConverter == null ? new JsonExposeAwareConverter(false) : jsonConverter;
88
89 encoding = FacesContext.getCurrentInstance().getExternalContext().getRequestCharacterEncoding();
90 if (encoding == null) {
91 encoding = "UTF-8";
92 }
93 }
94
95 /**
96 * Adds a request parameter to the URL without specifying a data type of the given parameter value. Parameter's value is converted to JSON notation when
97 * adding. Furthermore, it will be encoded according to the acquired encoding.
98 *
99 * @param name name of the request parameter
100 * @param value value of the request parameter
101 * @return RequestParameterBuilder updated this instance which can be reused
102 * @throws UnsupportedEncodingException DOCUMENT_ME
103 */
104 public RequestParameterBuilder paramJson(final String name, final Object value)
105 throws UnsupportedEncodingException {
106 return paramJson(name, value, null);
107 }
108
109 /**
110 * Adds a request parameter to the URL with specifying a data type of the given parameter value. Data type is sometimes required, especially for Java
111 * generic types, because type information is erased at runtime and the conversion to JSON will not work properly. Parameter's value is converted to JSON
112 * notation when adding. Furthermore, it will be encoded according to the acquired encoding.
113 *
114 * @param name name of the request parameter
115 * @param value value of the request parameter
116 * @param type data type of the value object. Any primitive type, array, non generic or generic type is supported. Data type is sometimes required to
117 * convert a value to a JSON representation. All data types should be fully qualified. Examples: "boolean" "int" "long[]" "java.lang.String"
118 * "java.util.Date" "java.util.Collection<java.lang.Integer>" "java.util.Map<java.lang.String, com.durr.FooPair<java.lang.Integer,
119 * java.util.Date>>" "com.durr.FooNonGenericClass" "com.durr.FooGenericClass<java.lang.String, java.lang.Integer>"
120 * "com.durr.FooGenericClass<int[], com.durr.FooGenericClass<com.durr.FooNonGenericClass, java.lang.Boolean>>".
121 * @return RequestParameterBuilder updated this instance which can be reused
122 * @throws UnsupportedEncodingException DOCUMENT_ME
123 */
124 public RequestParameterBuilder paramJson(final String name, final Object value, final String type)
125 throws UnsupportedEncodingException {
126 final String encodedJsonValue = encodeJson(value, type);
127
128 if (added || originalUrl.contains("?")) {
129 buffer.append("&");
130 }
131 else {
132 buffer.append("?");
133 }
134
135 buffer.append(name);
136 buffer.append("=");
137 buffer.append(encodedJsonValue);
138
139 // set a flag that at least one request parameter was added
140 added = true;
141
142 return this;
143 }
144
145 /**
146 * Adds a request parameter to the URL. This is a convenient method for primitive, plain data types. Parameter's value will not be converted to JSON
147 * notation when adding. It will be only encoded according to the acquired encoding. Note: null values will not be added.
148 *
149 * @param name name of the request parameter
150 * @param value value of the request parameter
151 * @return RequestParameterBuilder updated this instance which can be reused
152 * @throws UnsupportedEncodingException DOCUMENT_ME
153 */
154 public RequestParameterBuilder param(final String name, final Object value) throws UnsupportedEncodingException {
155 final String encodedValue = encode(value);
156
157 if (encodedValue == null) {
158 return this;
159 }
160
161 if (added || originalUrl.contains("?")) {
162 buffer.append("&");
163 }
164 else {
165 buffer.append("?");
166 }
167
168 buffer.append(name);
169 buffer.append("=");
170 buffer.append(encodedValue);
171
172 // set a flag that at least one request parameter was added
173 added = true;
174
175 return this;
176 }
177
178 /**
179 * Encodes given value with a proper encoding. This is a convenient method for primitive, plain data types. Value will not be converted to JSON. Note: Value
180 * can be null.
181 *
182 * @param value value to be encoded
183 * @return String encoded value
184 * @throws UnsupportedEncodingException DOCUMENT_ME
185 */
186 public String encode(final Object value) throws UnsupportedEncodingException {
187 if (value == null) {
188 return null;
189 }
190
191 return URLEncoder.encode(value.toString(), encoding);
192 }
193
194 /**
195 * Convertes give value to JSON and encodes the converted value with a proper encoding. Data type is sometimes required, especially for Java generic types,
196 * because type information is erased at runtime and the conversion to JSON will not work properly.
197 *
198 * @param value value to be converted and encoded
199 * @param type data type of the value object. Any primitive type, array, non generic or generic type is supported. Data type is sometimes required to
200 * convert a value to a JSON representation. All data types should be fully qualified. Examples: "boolean" "int" "long[]" "java.lang.String"
201 * "java.util.Date" "java.util.Collection<java.lang.Integer>" "java.util.Map<java.lang.String, com.durr.FooPair<java.lang.Integer,
202 * java.util.Date>>" "com.durr.FooNonGenericClass" "com.durr.FooGenericClass<java.lang.String, java.lang.Integer>"
203 * "com.durr.FooGenericClass<int[], com.durr.FooGenericClass<com.durr.FooNonGenericClass, java.lang.Boolean>>".
204 * @return String converted and encoded value
205 * @throws UnsupportedEncodingException DOCUMENT_ME
206 */
207 public String encodeJson(final Object value, final String type) throws UnsupportedEncodingException {
208 jsonConverter.setType(type);
209
210 final String jsonValue;
211 if (value == null) {
212 jsonValue = "null";
213 }
214 else {
215 jsonValue = jsonConverter.getAsString(null, null, value);
216 }
217
218 return URLEncoder.encode(jsonValue, encoding);
219 }
220
221 /**
222 * Convertes give value to JSON without to know the data type and encodes the converted value with a proper encoding.
223 *
224 * @param value value to be converted and encoded
225 * @return String converted and encoded value
226 * @throws UnsupportedEncodingException DOCUMENT_ME
227 */
228 public String encodeJson(final Object value) throws UnsupportedEncodingException {
229 return encodeJson(value, null);
230 }
231
232 /**
233 * Builds the end result.
234 *
235 * @return String end result
236 */
237 public String build() {
238 return buffer.toString();
239 }
240
241 /**
242 * Resets the internal state in order to be reused.
243 *
244 * @return RequestParameterBuilder reseted builder
245 */
246 public RequestParameterBuilder reset() {
247 buffer = new StringBuilder(originalUrl);
248 jsonConverter.setType(null);
249 added = false;
250
251 return this;
252 }
253 }