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 * <p>The JSON-RPC 2.0 specification and user group forum can be found 
087 * <a href="http://groups.google.com/group/json-rpc">here</a>.
088 * 
089 * @author Vladimir Dzhuvinov
090 */
091public class JSONRPC2Request extends JSONRPC2Message {
092
093        
094        /** 
095         * The method name. 
096         */
097        private String method;
098
099
100        /**
101         * The positional parameters, {@code null} if none.
102         */
103        private List<Object> positionalParams;
104
105
106        /**
107         * The named parameters, {@code null} if none.
108         */
109        private Map<String,Object> namedParams;
110        
111        
112        /** 
113         * The request identifier. 
114         */
115        private Object id;
116        
117        
118        /** 
119         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
120         *
121         * @param jsonString The JSON-RPC 2.0 request string, UTF-8 encoded. 
122         *                   Must not be {@code null}.
123         *
124         * @return The corresponding JSON-RPC 2.0 request object.
125         *
126         * @throws JSONRPC2ParseException With detailed message if parsing 
127         *                                failed.
128         */
129        public static JSONRPC2Request parse(final String jsonString)
130                throws JSONRPC2ParseException {
131                
132                return parse(jsonString, false, false, false);
133        }
134        
135        
136        /** 
137         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
138         *
139         * @param jsonString    The JSON-RPC 2.0 request string, UTF-8 encoded. 
140         *                      Must not be {@code null}.
141         * @param preserveOrder {@code true} to preserve the order of JSON 
142         *                      object members in parameters.
143         *
144         * @return The corresponding JSON-RPC 2.0 request object.
145         *
146         * @throws JSONRPC2ParseException With detailed message if parsing 
147         *                                failed.
148         */
149        public static JSONRPC2Request parse(final String jsonString, 
150                                            final boolean preserveOrder)
151                throws JSONRPC2ParseException {
152                
153                return parse(jsonString, preserveOrder, false, false);
154        }
155        
156        
157        /** 
158         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
159         *
160         * @param jsonString    The JSON-RPC 2.0 request string, UTF-8 encoded.
161         *                      Must not be {@code null}.
162         * @param preserveOrder {@code true} to preserve the order of JSON 
163         *                      object members in parameters.
164         * @param ignoreVersion {@code true} to skip a check of the 
165         *                      {@code "jsonrpc":"2.0"} version attribute in the 
166         *                      JSON-RPC 2.0 message.
167         *
168         * @return The corresponding JSON-RPC 2.0 request object.
169         *
170         * @throws JSONRPC2ParseException With detailed message if parsing 
171         *                                failed.
172         */
173        public static JSONRPC2Request parse(final String jsonString, 
174                                            final boolean preserveOrder, 
175                                            final boolean ignoreVersion)
176                throws JSONRPC2ParseException {
177                
178                return parse(jsonString, preserveOrder, ignoreVersion, false);
179        }
180        
181        
182        /** 
183         * Parses a JSON-RPC 2.0 request string. This method is thread-safe.
184         *
185         * @param jsonString            The JSON-RPC 2.0 request string, UTF-8 
186         *                              encoded. Must not be {@code null}.
187         * @param preserveOrder         {@code true} to preserve the order of
188         *                              JSON object members in parameters.
189         * @param ignoreVersion         {@code true} to skip a check of the 
190         *                              {@code "jsonrpc":"2.0"} version 
191         *                              attribute in the JSON-RPC 2.0 message.
192         * @param parseNonStdAttributes {@code true} to parse non-standard
193         *                              attributes found in the JSON-RPC 2.0 
194         *                              message.
195         *
196         * @return The corresponding JSON-RPC 2.0 request object.
197         *
198         * @throws JSONRPC2ParseException With detailed message if parsing 
199         *                                failed.
200         */
201        public static JSONRPC2Request parse(final String jsonString, 
202                                            final boolean preserveOrder, 
203                                            final boolean ignoreVersion, 
204                                            final boolean parseNonStdAttributes)
205                throws JSONRPC2ParseException {
206                
207                JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, 
208                                                           ignoreVersion, 
209                                                           parseNonStdAttributes);
210                
211                return parser.parseJSONRPC2Request(jsonString);
212        }
213        
214        
215        /** 
216         * Constructs a new JSON-RPC 2.0 request with no parameters.
217         *
218         * @param method The name of the requested method. Must not be 
219         *               {@code null}.
220         * @param id     The request identifier echoed back to the caller. 
221         *               The value must <a href="#map">map</a> to a JSON 
222         *               scalar ({@code null} and fractions, however, should
223         *               be avoided).
224         */
225        public JSONRPC2Request(final String method, final Object id) {
226                
227                setMethod(method);
228                setID(id);
229        }
230        
231        
232        /** 
233         * Constructs a new JSON-RPC 2.0 request with positional (JSON array)
234         * parameters.
235         *
236         * @param method           The name of the requested method. Must not 
237         *                         be {@code null}.
238         * @param positionalParams The positional (JSON array) parameters, 
239         *                         {@code null} if none.
240         * @param id               The request identifier echoed back to the 
241         *                         caller. The value must <a href="#map">map</a> 
242         *                         to a JSON scalar ({@code null} and 
243         *                         fractions, however, should be avoided).
244         */
245        public JSONRPC2Request(final String method, 
246                               final List<Object> positionalParams, 
247                               final Object id) {
248                
249                setMethod(method);
250                setPositionalParams(positionalParams);
251                setID(id);
252        }
253                
254        
255        /** 
256         * Constructs a new JSON-RPC 2.0 request with named (JSON object)
257         * parameters.
258         *
259         * @param method      The name of the requested method.
260         * @param namedParams The named (JSON object) parameters, {@code null} 
261         *                    if none.
262         * @param id          The request identifier echoed back to the caller. 
263         *                    The value must <a href="#map">map</a> to a JSON 
264         *                    scalar ({@code null} and fractions, however, 
265         *                    should be avoided).
266         */
267        public JSONRPC2Request(final String method, 
268                               final Map <String,Object> namedParams, 
269                               final Object id) {
270                
271                setMethod(method);
272                setNamedParams(namedParams);
273                setID(id);
274        }
275        
276        
277        /** 
278         * Gets the name of the requested method.
279         *
280         * @return The method name.
281         */
282        public String getMethod() {
283                
284                return method;
285        }
286        
287        
288        /**
289         * Sets the name of the requested method.
290         *
291         * @param method The method name. Must not be {@code null}.
292         */
293        public void setMethod(final String method) {
294                
295                // The method name is mandatory
296                if (method == null)
297                        throw new IllegalArgumentException("The method name must not be null");
298
299                this.method = method;
300        }
301        
302        
303        /** 
304         * Gets the parameters type ({@link JSONRPC2ParamsType#ARRAY positional}, 
305         * {@link JSONRPC2ParamsType#OBJECT named} or 
306         * {@link JSONRPC2ParamsType#NO_PARAMS none}).
307         *
308         * @return The parameters type.
309         */
310        public JSONRPC2ParamsType getParamsType() {
311        
312                if (positionalParams == null && namedParams == null)
313                        return JSONRPC2ParamsType.NO_PARAMS;
314
315                if (positionalParams != null)
316                        return JSONRPC2ParamsType.ARRAY;
317
318                if (namedParams != null)
319                        return JSONRPC2ParamsType.OBJECT;
320
321                else
322                        return JSONRPC2ParamsType.NO_PARAMS;
323        }
324        
325        
326        /** 
327         * Gets the request parameters.
328         *
329         * <p>This method was deprecated in version 1.30. Use
330         * {@link #getPositionalParams} or {@link #getNamedParams} instead.
331         *
332         * @return The parameters as {@code List&lt;Object&gt;} for positional
333         *         (JSON array), {@code Map&lt;String,Object&gt;} for named
334         *         (JSON object), or {@code null} if none.
335         */
336        @Deprecated
337        public Object getParams() {
338                
339                switch (getParamsType()) {
340
341                        case ARRAY:
342                                return positionalParams;
343
344                        case OBJECT:
345                                return namedParams;
346
347                        default:
348                                return null;
349                }
350        }
351
352
353        /**
354         * Gets the positional (JSON array) parameters.
355         *
356         * @since 1.30
357         *
358         * @return The positional (JSON array) parameters, {@code null} if none
359         *         or named.
360         */
361        public List<Object> getPositionalParams() {
362
363                return positionalParams;
364        }
365
366
367        /**
368         * Gets the named parameters.
369         *
370         * @since 1.30
371         *
372         * @return The named (JSON object) parameters, {@code null} if none or 
373         *         positional.
374         */
375        public Map<String,Object> getNamedParams() {
376
377                return namedParams;
378        }
379        
380        
381        /**
382         * Sets the request parameters.
383         *
384         * <p>This method was deprecated in version 1.30. Use
385         * {@link #setPositionalParams} or {@link #setNamedParams} instead.
386         *
387         * @param params The parameters. For positional (JSON array) pass a 
388         *               {@code List&lt;Object&gt;}. For named (JSON object)
389         *               pass a {@code Map&lt;String,Object&gt;}. If there are
390         *               no parameters pass {@code null}.
391         */
392        @Deprecated
393        @SuppressWarnings("unchecked")
394        public void setParams(final Object params) {
395        
396                if (params == null) {
397                        positionalParams = null;
398                        namedParams = null;
399                } else if (params instanceof List) {
400                        positionalParams = (List<Object>) params;
401                } else if (params instanceof Map) {
402                        namedParams = (Map<String, Object>) params;
403                } else {
404                        throw new IllegalArgumentException("The request parameters must be of type List, Map or null");
405                }
406        }
407
408
409        /**
410         * Sets the positional (JSON array) request parameters.
411         *
412         * @since 1.30
413         *
414         * @param positionalParams The positional (JSON array) request 
415         *                         parameters, {@code null} if none.
416         */
417        public void setPositionalParams(final List<Object> positionalParams) {
418
419                if (positionalParams == null)
420                        return;
421
422                this.positionalParams = positionalParams;
423        }
424
425
426        /**
427         * Sets the named (JSON object) request parameters.
428         *
429         * @since 1.30
430         *
431         * @param namedParams The named (JSON object) request parameters,
432         *                    {@code null} if none.
433         */
434        public void setNamedParams(final Map<String,Object> namedParams) {
435
436                if (namedParams == null)
437                        return;
438
439                this.namedParams = namedParams;
440        }
441        
442        
443        /** 
444         * Gets the request identifier.
445         *
446         * @return The request identifier ({@code Number}, {@code Boolean},
447         *         {@code String}) or {@code null}.
448         */
449        public Object getID() {
450                
451                return id;
452        }
453        
454        
455        /**
456         * Sets the request identifier (ID).
457         *
458         * @param id The request identifier echoed back to the caller. 
459         *           The value must <a href="#map">map</a> to a JSON 
460         *           scalar ({@code null} and fractions, however, should
461         *           be avoided).
462         */
463        public void setID(final Object id) {
464                
465                if (   id != null             &&
466                    ! (id instanceof Boolean) &&
467                    ! (id instanceof Number ) &&
468                    ! (id instanceof String )     )
469                        throw new IllegalArgumentException("The request identifier must map to a JSON scalar");
470                this.id = id;
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}