001    package com.thetransactioncompany.jsonrpc2;
002    
003    
004    import java.util.List;
005    import java.util.Map;
006    
007    import 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     */
091    public 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<Object>} for positional (JSON 
333             *         array), {@code Map<String,Object>} for named (JSON object),
334             *         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<Object>}. For named (JSON object) pass a
389             *               {@code Map<String,Object>}. If there are no 
390             *               parameters pass {@code null}.
391             */
392            @Deprecated
393            @SuppressWarnings("unchecked")
394            public void setParams(final Object params) {
395            
396                    if (params == null)
397                            return;
398                            
399                    else if (params instanceof List)
400                            positionalParams = (List<Object>)params;
401                            
402                    else if (params instanceof Map)
403                            namedParams = (Map<String,Object>)params;
404                            
405                    else
406                            throw new IllegalArgumentException("The request parameters must be of type List, Map or null");
407            }
408    
409    
410            /**
411             * Sets the positional (JSON array) request parameters.
412             *
413             * @since 1.30
414             *
415             * @param positionalParams The positional (JSON array) request 
416             *                         parameters, {@code null} if none.
417             */
418            public void setPositionalParams(final List<Object> positionalParams) {
419    
420                    if (positionalParams == null)
421                            return;
422    
423                    this.positionalParams = positionalParams;
424            }
425    
426    
427            /**
428             * Sets the named (JSON object) request parameters.
429             *
430             * @since 1.30
431             *
432             * @param namedParams The named (JSON object) request parameters,
433             *                    {@code null} if none.
434             */
435            public void setNamedParams(final Map<String,Object> namedParams) {
436    
437                    if (namedParams == null)
438                            return;
439    
440                    this.namedParams = namedParams;
441            }
442            
443            
444            /** 
445             * Gets the request identifier.
446             *
447             * @return The request identifier ({@code Number}, {@code Boolean},
448             *         {@code String}) or {@code null}.
449             */
450            public Object getID() {
451                    
452                    return id;
453            }
454            
455            
456            /**
457             * Sets the request identifier (ID).
458             *
459             * @param id The request identifier echoed back to the caller. 
460             *           The value must <a href="#map">map</a> to a JSON 
461             *           scalar ({@code null} and fractions, however, should
462             *           be avoided).
463             */
464            public void setID(final Object id) {
465                    
466                    if (   id != null             &&
467                        ! (id instanceof Boolean) &&
468                        ! (id instanceof Number ) &&
469                        ! (id instanceof String )     )
470                            throw new IllegalArgumentException("The request identifier must map to a JSON scalar");
471                    this.id = id;
472            }
473            
474            
475            @Override
476            public JSONObject toJSONObject() {
477            
478                    JSONObject req = new JSONObject();
479                    
480                    req.put("method", method);
481                    
482                    // The params can be omitted if none
483                    switch (getParamsType()) {
484    
485                            case ARRAY:
486                                    req.put("params", positionalParams);
487                                    break;
488    
489                            case OBJECT:
490                                    req.put("params", namedParams);
491                                    break;
492                    }
493                    
494                    req.put("id", id);
495                    
496                    req.put("jsonrpc", "2.0");
497                    
498                    Map <String,Object> nonStdAttributes = getNonStdAttributes();
499                    
500                    if (nonStdAttributes != null) {
501                    
502                            for (final Map.Entry<String,Object> attr: nonStdAttributes.entrySet())
503                                    req.put(attr.getKey(), attr.getValue());
504                    }
505                    
506                    return req;
507            }
508    }