001package com.thetransactioncompany.jsonrpc2;
002
003
004import java.util.HashMap;
005import java.util.List;
006import java.util.Map;
007
008import net.minidev.json.JSONAware;
009import 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 */
060public 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}