001    package com.thetransactioncompany.jsonrpc2;
002    
003    
004    import java.util.HashMap;
005    import java.util.List;
006    import java.util.Map;
007    
008    import net.minidev.json.JSONAware;
009    import net.minidev.json.JSONObject;
010    
011    
012    /**
013     * The base abstract class for JSON-RPC 2.0 requests, notifications and
014     * responses. Provides common methods for parsing (from JSON string) and
015     * serialisation (to JSON string) of these three message types.
016     *
017     * <p>Example parsing and serialisation back to JSON:
018     *
019     * <pre>
020     * String jsonString = "{\"method\":\"progressNotify\",\"params\":[\"75%\"],\"jsonrpc\":\"2.0\"}";
021     *
022     * JSONRPC2Message message = null;
023     *
024     * // parse
025     * try {
026     *        message = JSONRPC2Message.parse(jsonString);
027     * } catch (JSONRPC2ParseException e) {
028     *        // handle parse exception
029     * }
030     *
031     * if (message instanceof JSONRPC2Request)
032     *        System.out.println("The message is a request");
033     * else if (message instanceof JSONRPC2Notification)
034     *        System.out.println("The message is a notification");
035     * else if (message instanceof JSONRPC2Response)
036     *        System.out.println("The message is a response");
037     *
038     * // serialise back to JSON string
039     * System.out.println(message);
040     *
041     * </pre>
042     *
043     * <p id="map">The mapping between JSON and Java entities (as defined by the 
044     * underlying JSON Smart library): 
045     *
046     * <pre>
047     *     true|false  <--->  java.lang.Boolean
048     *     number      <--->  java.lang.Number
049     *     string      <--->  java.lang.String
050     *     array       <--->  java.util.List
051     *     object      <--->  java.util.Map
052     *     null        <--->  null
053     * </pre>
054     *
055     * <p>The JSON-RPC 2.0 specification and user group forum can be found 
056     * <a href="http://groups.google.com/group/json-rpc">here</a>.
057     * 
058     * @author Vladimir Dzhuvinov
059     */
060    public abstract class JSONRPC2Message implements JSONAware {
061    
062    
063            /**
064             * Map of non-standard JSON-RPC 2.0 message attributes, {@code null} if
065             * none.
066             */
067            private Map <String,Object> nonStdAttributes = null;
068            
069    
070            /** 
071             * Provides common parsing of JSON-RPC 2.0 requests, notifications 
072             * and responses. Use this method if you don't know which type of 
073             * JSON-RPC message the input JSON string represents.
074             *
075             * <p>Batched requests / notifications are not supported.
076             *
077             * <p>This method is thread-safe.
078             *
079             * <p>If you are certain about the message type use the dedicated 
080             * {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse} 
081             * or {@link JSONRPC2Response#parse} methods. They are more efficient 
082             * and provide a more detailed parse error reporting.
083             *
084             * <p>The member order of parsed JSON objects will not be preserved 
085             * (for efficiency reasons) and the JSON-RPC 2.0 version field must be 
086             * set to "2.0". To change this behaviour check the optional {@link 
087             * #parse(String,boolean,boolean)} method.
088             *
089             * @param jsonString A JSON string representing a JSON-RPC 2.0 request, 
090             *                   notification or response, UTF-8 encoded. Must not
091             *                   be {@code null}.
092             *
093             * @return An instance of {@link JSONRPC2Request}, 
094             *         {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
095             *
096             * @throws JSONRPC2ParseException With detailed message if parsing 
097             *                                failed.
098             */
099            public static JSONRPC2Message parse(final String jsonString)
100                    throws JSONRPC2ParseException {
101    
102                    return parse(jsonString, false, false);
103            }
104            
105            
106            /** 
107             * Provides common parsing of JSON-RPC 2.0 requests, notifications 
108             * and responses. Use this method if you don't know which type of 
109             * JSON-RPC message the input string represents.
110             *
111             * <p>Batched requests / notifications are not supported.
112             *
113             * <p>This method is thread-safe.
114             *
115             * <p>If you are certain about the message type use the dedicated 
116             * {@link JSONRPC2Request#parse}, {@link JSONRPC2Notification#parse} 
117             * or {@link JSONRPC2Response#parse} methods. They are more efficient 
118             * and provide a more detailed parse error reporting.
119             *
120             * @param jsonString    A JSON string representing a JSON-RPC 2.0 
121             *                      request, notification or response, UTF-8
122             *                      encoded. Must not be {@code null}.
123             * @param preserveOrder If {@code true} the member order of JSON objects
124             *                      in parameters and results must be preserved.
125             * @param ignoreVersion If {@code true} the {@code "jsonrpc":"2.0"}
126             *                      version field in the JSON-RPC 2.0 message will 
127             *                      not be checked.
128             *
129             * @return An instance of {@link JSONRPC2Request}, 
130             *         {@link JSONRPC2Notification} or {@link JSONRPC2Response}.
131             *
132             * @throws JSONRPC2ParseException With detailed message if parsing 
133             *                                failed.
134             */
135            public static JSONRPC2Message parse(final String jsonString, final boolean preserveOrder, final boolean ignoreVersion)
136                    throws JSONRPC2ParseException {
137                    
138                    JSONRPC2Parser parser = new JSONRPC2Parser(preserveOrder, ignoreVersion);
139                    
140                    return parser.parseJSONRPC2Message(jsonString);
141            }
142            
143            
144            /**
145             * Appends a non-standard attribute to this JSON-RPC 2.0 message. This is 
146             * done by adding a new member (key / value pair) to the top level JSON 
147             * object representing the message.
148             *
149             * <p>You may use this method to add meta and debugging attributes, 
150             * such as the request processing time, to a JSON-RPC 2.0 message.
151             *
152             * @param name  The attribute name. Must not conflict with the existing
153             *              "method", "id", "params", "result", "error" and "jsonrpc"
154             *              attributes reserved by the JSON-RPC 2.0 protocol, else 
155             *              an {@code IllegalArgumentException} will be thrown. Must
156             *              not be {@code null} either.
157             * @param value The attribute value. Must be of type String, boolean,
158             *              number, List, Map or null, else an
159             *              {@code IllegalArgumentException} will be thrown.
160             */
161            public void appendNonStdAttribute(final String name, final Object value) {
162            
163                    // Name check
164                    if (name == null          ||
165                        name.equals("method") ||
166                        name.equals("id")     ||
167                        name.equals("params") ||
168                        name.equals("result") ||
169                        name.equals("error")  ||
170                        name.equals("jsonrpc")   )
171            
172                            throw new IllegalArgumentException("Non-standard attribute name violation");
173            
174                    // Value check
175                    if ( value != null                &&
176                         ! (value instanceof Boolean) &&
177                         ! (value instanceof Number)  &&
178                         ! (value instanceof String)  &&
179                         ! (value instanceof List)    &&
180                         ! (value instanceof Map)        )
181                         
182                            throw new IllegalArgumentException("Illegal non-standard attribute value, must map to a valid JSON type");
183                    
184                    
185                    if (nonStdAttributes == null)
186                            nonStdAttributes = new HashMap<String,Object>();
187                    
188                    nonStdAttributes.put(name, value);
189            }
190            
191            
192            /**
193             * Retrieves a non-standard JSON-RPC 2.0 message attribute.
194             *
195             * @param name The name of the non-standard attribute to retrieve. Must
196             *             not be {@code null}.
197             *
198             * @return The value of the non-standard attribute (may also be 
199             *         {@code null}, {@code null} if not found.
200             */
201            public Object getNonStdAttribute(final String name) {
202            
203                    if (nonStdAttributes == null)
204                            return null;
205                    
206                    return nonStdAttributes.get(name);
207            }
208            
209            
210            /**
211             * Retrieves the non-standard JSON-RPC 2.0 message attributes.
212             *
213             * @return The non-standard attributes as a map, {@code null} if none.
214             */
215            public Map<String,Object> getNonStdAttributes() {
216            
217                    return nonStdAttributes;
218            }
219            
220            
221            /** 
222             * Returns a JSON object representing this JSON-RPC 2.0 message.
223             *
224             * @return The JSON object.
225             */
226            public abstract JSONObject toJSONObject();
227            
228            
229            /**
230             * Returns a JSON string representation of this JSON-RPC 2.0 message.
231             *
232             * @see #toString
233             *
234             * @return The JSON object string representing this JSON-RPC 2.0 
235             *         message.
236             */
237            @Override
238            public String toJSONString() {
239            
240                    return toString();
241            }
242            
243            
244            /** 
245             * Serialises this JSON-RPC 2.0 message to a JSON object string.
246             *
247             * @return The JSON object string representing this JSON-RPC 2.0 
248             *         message.
249             */
250            @Override
251            public String toString() {
252                    
253                    return toJSONObject().toString();
254            }
255    }