001package com.thetransactioncompany.jsonrpc2;
002
003
004import java.util.List;
005import java.util.Map;
006
007import net.minidev.json.JSONObject;
008
009
010/** 
011 * Represents a JSON-RPC 2.0 request. 
012 *
013 * <p>A request carries four pieces of data:
014 * <ul>
015 *     <li>{@code method} The name of the remote method to call.
016 *     <li>{@code params} The required method parameters (if any), which can 
017 *         be packed into a JSON array or object.
018 *     <li>{@code id} An identifier which is echoed back to the client with 
019 *         the response.
020 *     <li>{@code jsonrpc} A string indicating the JSON-RPC protocol version 
021 *         set to "2.0".
022 * </ul>
023 *
024 * <p>Here is a sample JSON-RPC 2.0 request string:
025 *
026 * <pre>
027 * {  
028 *    "method"  : "makePayment",
029 *    "params"  : { "recipient" : "Penny Adams", "amount":175.05 },
030 *    "id"      : "0001",
031 *    "jsonrpc" : "2.0"
032 * }
033 * </pre>
034 *
035 * <p>This class provides two methods to obtain a request object:
036 * <ul>
037 *     <li>Pass a JSON-RPC 2.0 request string to the static 
038 *         {@link #parse} method, or 
039 *     <li>Invoke one of the constructors with the appropriate arguments.
040 * </ul>
041 *
042 * <p>Example 1: Parsing a request string:
043 *
044 * <pre>
045 * String jsonString = "{\"method\":\"makePayment\"," +
046 *                     "\"params\":{\"recipient\":\"Penny Adams\",\"amount\":175.05}," +
047 *                     "\"id\":\"0001\","+
048 *                     "\"jsonrpc\":\"2.0\"}";
049 * 
050 * JSONRPC2Request req = null;
051 * 
052 * try {
053 *         req = JSONRPC2Request.parse(jsonString);
054 *
055 * } catch (JSONRPC2ParseException e) {
056 *         // handle exception
057 * }
058 * </pre>
059 *
060 * <p>Example 2: Recreating the above request:
061 * 
062 * <pre>
063 * String method = "makePayment";
064 * Map&lt;String,Object&gt; params = new HashMap&lt;String,Object&gt;();
065 * params.put("recipient", "Penny Adams");
066 * params.put("amount", 175.05);
067 * String id = "0001";
068 *
069 * JSONRPC2Request req = new JSONRPC2Request(method, params, id);
070 *
071 * System.out.println(req);
072 * </pre>
073 *
074 * <p id="map">The mapping between JSON and Java entities (as defined by the 
075 * underlying JSON Smart library): 
076 *
077 * <pre>
078 *     true|false  <--->  java.lang.Boolean
079 *     number      <--->  java.lang.Number
080 *     string      <--->  java.lang.String
081 *     array       <--->  java.util.List
082 *     object      <--->  java.util.Map
083 *     null        <--->  null
084 * </pre>
085 * 
086 * @author Vladimir Dzhuvinov
087 */
088public class JSONRPC2Request extends JSONRPC2Message {
089
090        
091        /** 
092         * The method name. 
093         */
094        private String method;
095
096
097        /**
098         * The positional parameters, {@code null} if none.
099         */
100        private List<Object> positionalParams;
101
102
103        /**
104         * The named parameters, {@code null} if none.
105         */
106        private Map<String,Object> namedParams;
107        
108        
109        /** 
110         * The request identifier. 
111         */
112        private Object id;
113        
114        
115        /** 
116         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
117         *
118         * @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded. 
119         *                   Must not be {@code null}.
120         *
121         * @return The corresponding JSON-RPC 2.0 request object.
122         *
123         * @throws JSONRPC2ParseException With detailed message if parsing 
124         *                                failed.
125         */
126        public static JSONRPC2Request parse(final String jsonString)
127                throws JSONRPC2ParseException {
128                
129                return parse(jsonString, false, false, false);
130        }
131        
132        
133        /** 
134         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
135         *
136         * @param jsonString    The JSON-RPC 2.0 request string, UTF-8 encoded. 
137         *                      Must not be {@code null}.
138         * @param preserveOrder {@code true} to preserve the order of JSON 
139         *                      object members in parameters.
140         *
141         * @return The corresponding JSON-RPC 2.0 request object.
142         *
143         * @throws JSONRPC2ParseException With detailed message if parsing 
144         *                                failed.
145         */
146        public static JSONRPC2Request parse(final String jsonString, 
147                                            final boolean preserveOrder)
148                throws JSONRPC2ParseException {
149                
150                return parse(jsonString, preserveOrder, false, false);
151        }
152        
153        
154        /** 
155         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
156         *
157         * @param jsonString    The JSON-RPC 2.0 request string, UTF-8 encoded.
158         *                      Must not be {@code null}.
159         * @param preserveOrder {@code true} to preserve the order of JSON 
160         *                      object members in parameters.
161         * @param ignoreVersion {@code true} to skip a check of the 
162         *                      {@code "jsonrpc":"2.0"} version attribute in the 
163         *                      JSON-RPC 2.0 message.
164         *
165         * @return The corresponding JSON-RPC 2.0 request object.
166         *
167         * @throws JSONRPC2ParseException With detailed message if parsing 
168         *                                failed.
169         */
170        public static JSONRPC2Request parse(final String jsonString, 
171                                            final boolean preserveOrder, 
172                                            final boolean ignoreVersion)
173                throws JSONRPC2ParseException {
174                
175                return parse(jsonString, preserveOrder, ignoreVersion, false);
176        }
177        
178        
179        /** 
180         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
181         *
182         * @param jsonString            The JSON-RPC 2.0 request string, UTF-8 
183         *                              encoded. Must not be {@code null}.
184         * @param preserveOrder         {@code true} to preserve the order of
185         *                              JSON object members in parameters.
186         * @param ignoreVersion         {@code true} to skip a check of the 
187         *                              {@code "jsonrpc":"2.0"} version 
188         *                              attribute in the JSON-RPC 2.0 message.
189         * @param parseNonStdAttributes {@code true} to parse non-standard
190         *                              attributes found in the JSON-RPC 2.0 
191         *                              message.
192         *
193         * @return The corresponding JSON-RPC 2.0 request object.
194         *
195         * @throws JSONRPC2ParseException With detailed message if parsing 
196         *                                failed.
197         */
198        public static JSONRPC2Request parse(final String jsonString, 
199                                            final boolean preserveOrder, 
200                                            final boolean ignoreVersion, 
201                                            final boolean parseNonStdAttributes)
202                throws JSONRPC2ParseException {
203                
204                JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, 
205                                                           ignoreVersion, 
206                                                           parseNonStdAttributes);
207                
208                return parser.parseJSONRPC2Request(jsonString);
209        }
210        
211        
212        /** 
213         * Constructs a new JSON-RPC 2.0 request with no parameters.
214         *
215         * @param method The name of the requested method. Must not be 
216         *               {@code null}.
217         * @param id     The request identifier echoed back to the caller. 
218         *               The value must <a href="#map">map</a> to a JSON 
219         *               scalar ({@code null} and fractions, however, should
220         *               be avoided).
221         */
222        public JSONRPC2Request(final String method, final Object id) {
223                
224                setMethod(method);
225                setID(id);
226        }
227        
228        
229        /** 
230         * Constructs a new JSON-RPC 2.0 request with positional (JSON array)
231         * parameters.
232         *
233         * @param method           The name of the requested method. Must not 
234         *                         be {@code null}.
235         * @param positionalParams The positional (JSON array) parameters, 
236         *                         {@code null} if none.
237         * @param id               The request identifier echoed back to the 
238         *                         caller. The value must <a href="#map">map</a> 
239         *                         to a JSON scalar ({@code null} and 
240         *                         fractions, however, should be avoided).
241         */
242        public JSONRPC2Request(final String method, 
243                               final List<Object> positionalParams, 
244                               final Object id) {
245                
246                setMethod(method);
247                setPositionalParams(positionalParams);
248                setID(id);
249        }
250                
251        
252        /** 
253         * Constructs a new JSON-RPC 2.0 request with named (JSON object)
254         * parameters.
255         *
256         * @param method      The name of the requested method.
257         * @param namedParams The named (JSON object) parameters, {@code null} 
258         *                    if none.
259         * @param id          The request identifier echoed back to the caller. 
260         *                    The value must <a href="#map">map</a> to a JSON 
261         *                    scalar ({@code null} and fractions, however, 
262         *                    should be avoided).
263         */
264        public JSONRPC2Request(final String method, 
265                               final Map <String,Object> namedParams, 
266                               final Object id) {
267                
268                setMethod(method);
269                setNamedParams(namedParams);
270                setID(id);
271        }
272        
273        
274        /** 
275         * Gets the name of the requested method.
276         *
277         * @return The method name.
278         */
279        public String getMethod() {
280                
281                return method;
282        }
283        
284        
285        /**
286         * Sets the name of the requested method.
287         *
288         * @param method The method name. Must not be {@code null}.
289         */
290        public void setMethod(final String method) {
291                
292                // The method name is mandatory
293                if (method == null)
294                        throw new IllegalArgumentException("The method name must not be null");
295
296                this.method = method;
297        }
298        
299        
300        /** 
301         * Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional}, 
302         * {@link JSONRPC2ParamsType#OBJECT named} or 
303         * {@link JSONRPC2ParamsType#NO_PARAMS none}).
304         *
305         * @return The parameters type.
306         */
307        public JSONRPC2ParamsType getParamsType() {
308        
309                if (positionalParams == null && namedParams == null)
310                        return JSONRPC2ParamsType.NO_PARAMS;
311
312                if (positionalParams != null)
313                        return JSONRPC2ParamsType.ARRAY;
314
315                if (namedParams != null)
316                        return JSONRPC2ParamsType.OBJECT;
317
318                else
319                        return JSONRPC2ParamsType.NO_PARAMS;
320        }
321        
322        
323        /** 
324         * Gets the request parameters.
325         *
326         * <p>This method was deprecated in version 1.30. Use
327         * {@link #getPositionalParams} or {@link #getNamedParams} instead.
328         *
329         * @return The parameters as {@code List&lt;Object&gt;} for positional
330         *         (JSON array), {@code Map&lt;String,Object&gt;} for named
331         *         (JSON object), or {@code null} if none.
332         */
333        @Deprecated
334        public Object getParams() {
335                
336                switch (getParamsType()) {
337
338                        case ARRAY:
339                                return positionalParams;
340
341                        case OBJECT:
342                                return namedParams;
343
344                        default:
345                                return null;
346                }
347        }
348
349
350        /**
351         * Gets the positional (JSON array) parameters.
352         *
353         * @since 1.30
354         *
355         * @return The positional (JSON array) parameters, {@code null} if none
356         *         or named.
357         */
358        public List<Object> getPositionalParams() {
359
360                return positionalParams;
361        }
362
363
364        /**
365         * Gets the named parameters.
366         *
367         * @since 1.30
368         *
369         * @return The named (JSON object) parameters, {@code null} if none or 
370         *         positional.
371         */
372        public Map<String,Object> getNamedParams() {
373
374                return namedParams;
375        }
376        
377        
378        /**
379         * Sets the request parameters.
380         *
381         * <p>This method was deprecated in version 1.30. Use
382         * {@link #setPositionalParams} or {@link #setNamedParams} instead.
383         *
384         * @param params The parameters. For positional (JSON array) pass a 
385         *               {@code List&lt;Object&gt;}. For named (JSON object)
386         *               pass a {@code Map&lt;String,Object&gt;}. If there are
387         *               no parameters pass {@code null}.
388         */
389        @Deprecated
390        @SuppressWarnings("unchecked")
391        public void setParams(final Object params) {
392        
393                if (params == null) {
394                        positionalParams = null;
395                        namedParams = null;
396                } else if (params instanceof List) {
397                        positionalParams = (List<Object>) params;
398                } else if (params instanceof Map) {
399                        namedParams = (Map<String, Object>) params;
400                } else {
401                        throw new IllegalArgumentException("The request parameters must be of type List, Map or null");
402                }
403        }
404
405
406        /**
407         * Sets the positional (JSON array) request parameters.
408         *
409         * @since 1.30
410         *
411         * @param positionalParams The positional (JSON array) request 
412         *                         parameters, {@code null} if none.
413         */
414        public void setPositionalParams(final List<Object> positionalParams) {
415
416                if (positionalParams == null)
417                        return;
418
419                this.positionalParams = positionalParams;
420        }
421
422
423        /**
424         * Sets the named (JSON object) request parameters.
425         *
426         * @since 1.30
427         *
428         * @param namedParams The named (JSON object) request parameters,
429         *                    {@code null} if none.
430         */
431        public void setNamedParams(final Map<String,Object> namedParams) {
432
433                if (namedParams == null)
434                        return;
435
436                this.namedParams = namedParams;
437        }
438        
439        
440        /** 
441         * Gets the request identifier.
442         *
443         * @return The request identifier ({@code Number}, {@code Boolean},
444         *         {@code String}) or {@code null}.
445         */
446        public Object getID() {
447                
448                return id;
449        }
450        
451        
452        /**
453         * Sets the request identifier (ID).
454         *
455         * @param id The request identifier echoed back to the caller. 
456         *           The value must <a href="#map">map</a> to a JSON 
457         *           scalar ({@code null} and fractions, however, should
458         *           be avoided).
459         */
460        public void setID(final Object id) {
461                
462                if (id == null            ||
463                    id instanceof Boolean ||
464                    id instanceof Number  ||
465                    id instanceof String
466        ) {
467                this.id = id;
468        } else {
469                        this.id = id.toString();
470        }
471        }
472        
473        
474        @Override
475        public JSONObject toJSONObject() {
476        
477                JSONObject req = new JSONObject();
478                
479                req.put("method", method);
480                
481                // The params can be omitted if none
482                switch (getParamsType()) {
483
484                        case ARRAY:
485                                req.put("params", positionalParams);
486                                break;
487
488                        case OBJECT:
489                                req.put("params", namedParams);
490                                break;
491                }
492                
493                req.put("id", id);
494                
495                req.put("jsonrpc", "2.0");
496                
497                Map <String,Object> nonStdAttributes = getNonStdAttributes();
498                
499                if (nonStdAttributes != null) {
500                
501                        for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
502                                req.put(attr.getKey(), attr.getValue());
503                }
504                
505                return req;
506        }
507}