├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── jsonrpc-java ├── build │ ├── bump-version.sh │ ├── merge.sh │ └── release.sh ├── pom.xml └── src │ ├── main │ ├── assembly │ │ └── assembly-descriptor.xml │ └── java │ │ └── org │ │ └── json │ │ └── rpc │ │ ├── client │ │ ├── HttpJsonRpcClientTransport.java │ │ ├── JsonRpcClientTransport.java │ │ └── JsonRpcInvoker.java │ │ ├── commons │ │ ├── AllowAllTypeChecker.java │ │ ├── GsonTypeChecker.java │ │ ├── JsonRpcClientException.java │ │ ├── JsonRpcErrorCodes.java │ │ ├── JsonRpcException.java │ │ ├── JsonRpcRemoteException.java │ │ ├── RpcIntroSpection.java │ │ └── TypeChecker.java │ │ └── server │ │ ├── HandleEntry.java │ │ ├── JsonRpcExecutor.java │ │ ├── JsonRpcServerTransport.java │ │ └── JsonRpcServletTransport.java │ └── test │ ├── java │ └── org │ │ └── json │ │ └── rpc │ │ ├── JsonRpcTest.java │ │ ├── client │ │ └── JsonRpcInvokerTest.java │ │ ├── commons │ │ └── GsonTypeCheckerTest.java │ │ └── server │ │ ├── CyclicReferenceBugImpl.java │ │ └── JsonRpcExecutorTest.java │ └── resources │ └── log4j.properties └── jsonrpc-js └── jsonrpc.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Project Files # 2 | ################# 3 | .idea 4 | *.ipr 5 | *.iml 6 | .settings 7 | .project 8 | .classpath 9 | 10 | # Maven Files # 11 | ############### 12 | pom.xml.* 13 | release.properties 14 | target 15 | 16 | # Compiled source # 17 | ################### 18 | *.com 19 | *.class 20 | *.dll 21 | *.exe 22 | *.o 23 | *.so 24 | 25 | # Packages # 26 | ############ 27 | # it's better to unpack these files and commit the raw source 28 | # git has its own built in compression methods 29 | *.7z 30 | *.dmg 31 | *.gz 32 | *.iso 33 | *.jar 34 | *.rar 35 | *.tar 36 | *.zip 37 | 38 | # Logs and databases # 39 | ###################### 40 | *.log 41 | *.sqlite 42 | 43 | # OS generated files # 44 | ###################### 45 | .DS_Store 46 | .DS_Store? 47 | ._* 48 | .Spotlight-V100 49 | .Trashes 50 | Icon? 51 | ehthumbs.db 52 | Thumbs.db 53 | 54 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk6 4 | 5 | branches: 6 | only: 7 | - master 8 | - develop 9 | - /^release-.*$/ 10 | - /^hotfix-.*$/ 11 | 12 | script: "cd jsonrpc-java/ && mvn clean install" 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 ritwik.net 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | use this file except in compliance with the License. You may obtain a copy of 5 | the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations under 13 | the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JsonRpc 2 | 3 | [![Build Status](https://travis-ci.org/RitwikSaikia/jsonrpc.png?branch=master)](https://travis-ci.org/RitwikSaikia/jsonrpc) 4 | 5 | JSON-RPC is a Java library implementing a very light weight client/server functionality of JSON-RPC protocol. 6 | Server/Client API is designed in such a way that, you don't have to worry about the details of the protocol. 7 | Just register the implementation classes in the server side, and create remote proxy objects at the client sides on the fly. 8 | 9 | The API works well in **Android/Google App Engine/Javascript** applications. 10 |




11 | 12 | # Usage 13 | 14 | ## Defining Interfaces 15 | 16 | Lets pickup an example of a Calculator service. 17 | 18 | The client side interface will be 19 | ```java 20 | public interface Calculator { 21 | 22 | double add(double x, double y); 23 | 24 | double multiply(double x, double y); 25 | 26 | } 27 | ``` 28 | 29 | The server side implementation will be 30 | ```java 31 | public class SimpleCalculatorImpl implements Calculator { 32 | 33 | public double add(double x, double y) { 34 | return x + y; 35 | } 36 | 37 | public double multiply(double x, double y) { 38 | return x * y; 39 | } 40 | 41 | } 42 | ``` 43 | 44 | ## Hosting the service 45 | 46 | ### Binding Service Implementation 47 | 48 | Once the service is ready, it needs to be bound to the JSON-RPC Server to make it available. 49 | ```java 50 | private JsonRpcExecutor bind() { 51 | JsonRpcExecutor executor = new JsonRpcExecutor(); 52 | 53 | Calculator calcImpl = new SimpleCalculatorImpl(); 54 | executor.addHandler("calc", calcImpl, Calculator.class); 55 | 56 | // add more services here 57 | 58 | return executor; 59 | } 60 | ``` 61 | 62 | ### Hosting with a Servlet 63 | ``` 64 | public class JsonRpcServlet extends HttpServlet { 65 | 66 | private final JsonRpcExecutor executor; 67 | 68 | public JsonRpcServlet() { 69 | executor = bind(); 70 | } 71 | 72 | private JsonRpcExecutor bind() { 73 | JsonRpcExecutor executor = new JsonRpcExecutor(); 74 | 75 | Calculator calcImpl = new SimpleCalculatorImpl(); 76 | executor.addHandler("calc", calcImpl, Calculator.class); 77 | // add more services here 78 | 79 | return executor; 80 | } 81 | 82 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 83 | executor.execute(new JsonRpcServletTransport(req, resp)); 84 | } 85 | 86 | } 87 | ``` 88 | 89 | ## Calling the service 90 | 91 | ### Call from Java Client 92 | 93 | ```java 94 | // where the servlet is hosted 95 | String url = "http://127.0.0.1:8080/jsonrpc"; 96 | 97 | HttpJsonRpcClientTransport transport = new HttpJsonRpcClientTransport(new URL(url)); 98 | 99 | JsonRpcInvoker invoker = new JsonRpcInvoker(); 100 | Calculator calc = invoker.get(transport, "calc", Calculator.class); 101 | 102 | double result = calc.add(1.2, 7.5); 103 | ``` 104 | 105 | **Exception Handling** 106 | * In case of remote exception it throws a JsonRpcRemoteException 107 | * invalid request 108 | * unable to parse json response 109 | * unknown method 110 | * any custom exception thrown by the application (SimpleCalculator) will appear with a full stack trace at the client side. 111 | * In case of local exception it throws a JsonRpcClientException 112 | * unable to reach service end point 113 | * unable to parse json response 114 | 115 | ### Call from JavaScript Client 116 | 117 | ```javascript 118 | 119 | 147 | ``` 148 | 149 | **Exception Handling** 150 | In case of remote exception it throws an _Error_ 151 | 152 | ## Dependencies 153 | * [Gson 1.4](http://code.google.com/p/google-gson/) 154 | * [SLF4J 1.5.8](http://www.slf4j.org/) 155 | 156 | ### Maven 157 | ```xml 158 | 159 | 160 | org.json.rpc 161 | jsonrpc 162 | 1.1 163 | 164 | 165 | 166 | 167 | 168 | ritwik-mvn-repo 169 | http://ritwik.net/mvn/releases/ 170 | 171 | 172 | ``` 173 | Note: classifier = server/client can be used to include only specific implementations. 174 | 175 | ## Logging 176 | JSON-RPC uses SLF4J for logging, so you will have to include specific implementation based on your requirement 177 | 178 | ### No Logging 179 | ```xml 180 | 181 | org.slf4j 182 | slf4j-nop 183 | 1.5.8 184 | 185 | ``` 186 | 187 | ### Log4j 188 | ```xml 189 | 190 | org.slf4j 191 | slf4j-log4j12 192 | 1.5.8 193 | 194 | 195 | 196 | log4j 197 | log4j 198 | 1.2.16 199 | 200 | ``` 201 | 202 | ### Android 203 | ```xml 204 | 205 | org.json.rpc 206 | jsonrpc 207 | 1.0 208 | client 209 | 210 | 211 | org.slf4j 212 | slf4j-api 213 | 214 | 215 | 216 | 217 | 218 | org.slf4j 219 | slf4j-android 220 | 1.6.1-RC1 221 | 222 | ``` 223 | -------------------------------------------------------------------------------- /jsonrpc-java/build/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import subprocess 4 | import sys 5 | import os 6 | 7 | def main(args): 8 | os.chdir("../") 9 | 10 | plugin="org.apache.maven.plugins:maven-release-plugin:2.3.2" 11 | 12 | mvn = "mvn" 13 | update = plugin + ":update-versions" 14 | 15 | cmd = [mvn, update, "-DautoVersionSubmodules=true", "-B"] 16 | if len(args) > 1: 17 | if len(args) > 2: 18 | usage() 19 | return 20 | else: 21 | cmd.append("-DdevelopmentVersion=%s" % args[1]) 22 | 23 | subprocess.Popen(cmd).communicate() 24 | 25 | def usage(): 26 | print "Usage bump-version.sh []" 27 | sys.exit() 28 | 29 | 30 | main(sys.argv) 31 | 32 | -------------------------------------------------------------------------------- /jsonrpc-java/build/merge.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import subprocess 4 | import sys 5 | import os 6 | import re 7 | 8 | def exec_cmd(cmd): 9 | p = subprocess.Popen(cmd) 10 | p.communicate() 11 | return p.wait() 12 | 13 | def main(args): 14 | os.chdir("../") 15 | 16 | git = "git" 17 | 18 | cmd = [git, "rev-parse", "--abbrev-ref", "HEAD"] 19 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 20 | branch = p.stdout.readline().strip() 21 | code = p.wait() 22 | if code != 0: 23 | print "unable to detect git branch" 24 | sys.exit(1) 25 | 26 | if not re.search("^(release|hotfix)-", branch): 27 | print "not on release or hotfix branch" 28 | sys.exit(1) 29 | 30 | cmd = [git, "checkout", "develop"] 31 | code = exec_cmd(cmd) 32 | if code != 0: 33 | print "unable to checkout 'develop'" 34 | sys.exit(1) 35 | 36 | cmd = [git, "merge", "--no-ff", branch] 37 | code = exec_cmd(cmd) 38 | if code != 0: 39 | print "unable to merge '" + branch + "' to 'develop'" 40 | sys.exit(1) 41 | 42 | cmd = [git, "checkout", "master"] 43 | code = exec_cmd(cmd) 44 | if code != 0: 45 | print "unable to checkout 'master'" 46 | sys.exit(1) 47 | 48 | cmd = [git, "merge", "--no-ff", branch + "~1"] 49 | code = exec_cmd(cmd) 50 | if code != 0: 51 | print "unable to merge '" + branch + "~1' to 'master'" 52 | sys.exit(1) 53 | 54 | cmd = [git, "checkout", branch] 55 | code = exec_cmd(cmd) 56 | if code != 0: 57 | print "unable to checkout '" + branch + "'" 58 | sys.exit(1) 59 | 60 | 61 | 62 | main(sys.argv) 63 | 64 | -------------------------------------------------------------------------------- /jsonrpc-java/build/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import subprocess 4 | import sys 5 | import os 6 | import re 7 | 8 | def exec_cmd(cmd): 9 | p = subprocess.Popen(cmd) 10 | p.communicate() 11 | return p.wait() 12 | 13 | def main(args): 14 | os.chdir("../") 15 | 16 | plugin="org.apache.maven.plugins:maven-release-plugin:2.3.2" 17 | mvn = "mvn" 18 | git = "git" 19 | 20 | prepare = plugin + ":prepare" 21 | perform = plugin + ":perform" 22 | 23 | cmd = [git, "rev-parse", "--abbrev-ref", "HEAD"] 24 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE) 25 | branch = p.stdout.readline().strip() 26 | code = p.wait() 27 | if code != 0: 28 | print "unable to detect git branch" 29 | sys.exit(1) 30 | 31 | if not re.search("^(release|hotfix)-", branch): 32 | print "not on release or hotfix branch" 33 | sys.exit(1) 34 | 35 | cmd = [mvn, prepare, "-DdryRun=true", "-B"] 36 | code = exec_cmd(cmd) 37 | if code != 0: 38 | print "unable to run release dryrun" 39 | sys.exit(1) 40 | 41 | cmd = [mvn, prepare, "-B"] 42 | code = exec_cmd(cmd) 43 | if code != 0: 44 | print "unable to release" 45 | sys.exit(1) 46 | 47 | 48 | cmd = [mvn, perform, "-B"] 49 | code = exec_cmd(cmd) 50 | if code != 0: 51 | print "unable to perform" 52 | sys.exit(1) 53 | 54 | main(sys.argv) 55 | 56 | -------------------------------------------------------------------------------- /jsonrpc-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | org.json.rpc 5 | jsonrpc 6 | JsonRpc 7 | 1.1 8 | 9 | 10 | 1.5 11 | UTF-8 12 | UTF-8 13 | 14 | 15 | 16 | 17 | com.google.code.gson 18 | gson 19 | 2.2.2 20 | 21 | 22 | 23 | javax 24 | javaee-api 25 | 6.0 26 | provided 27 | 28 | 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | 1.5.8 34 | 35 | 36 | 37 | org.slf4j 38 | slf4j-nop 39 | 1.5.8 40 | provided 41 | 42 | 43 | 44 | 45 | org.slf4j 46 | slf4j-log4j12 47 | 1.5.8 48 | test 49 | 50 | 51 | org.testng 52 | testng 53 | 5.8 54 | test 55 | jdk15 56 | 57 | 58 | org.easymock 59 | easymock 60 | 2.4 61 | test 62 | 63 | 64 | org.easymock 65 | easymockclassextension 66 | 2.4 67 | test 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.apache.maven.plugins 76 | maven-compiler-plugin 77 | 2.0 78 | 79 | ${java.version} 80 | ${java.version} 81 | 82 | 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-jar-plugin 87 | 2.3.1 88 | 89 | 90 | all-jar 91 | package 92 | 93 | jar 94 | 95 | 96 | 97 | client-jar 98 | package 99 | 100 | jar 101 | 102 | 103 | client 104 | 105 | true 106 | 107 | 108 | org/json/rpc/commons/** 109 | org/json/rpc/client/** 110 | 111 | 112 | 113 | 114 | server-jar 115 | package 116 | 117 | jar 118 | 119 | 120 | server 121 | 122 | true 123 | 124 | 125 | org/json/rpc/commons/** 126 | org/json/rpc/server/** 127 | 128 | 129 | 130 | 131 | 132 | 133 | true 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-source-plugin 141 | 2.1.2 142 | 143 | 144 | package 145 | 146 | jar 147 | 148 | 149 | 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-release-plugin 154 | 2.3.2 155 | 156 | jsonrpc-@{project.version} 157 | true 158 | false 159 | false 160 | false 161 | install 162 | 163 | 164 | 165 | 166 | org.apache.maven.plugins 167 | maven-javadoc-plugin 168 | 2.7 169 | 170 | 171 | package 172 | 173 | jar 174 | 175 | 176 | 177 | 178 | 179 | http://download.oracle.com/javase/1.5.0/docs/api/ 180 | 181 | true 182 | public 183 | 184 | 185 | 186 | maven-assembly-plugin 187 | 2.2 188 | 189 | 190 | package 191 | 192 | single 193 | 194 | 195 | 196 | 197 | src/main/assembly/assembly-descriptor.xml 198 | ${project.artifactId}-${project.version} 199 | false 200 | 201 | 202 | 203 | org.codehaus.mojo 204 | cobertura-maven-plugin 205 | 2.5.2 206 | 207 | 208 | false 209 | 60 210 | 75 211 | 60 212 | 75 213 | 75 214 | 60 215 | 216 | 217 | 218 | **/*Exception.class 219 | **/*Constants.class 220 | 221 | 222 | 223 | 224 | 225 | package 226 | 227 | check 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | scm:git:https://github.com/RitwikSaikia/jsonrpc.git 239 | scm:git:git@github.com:RitwikSaikia/jsonrpc.git 240 | https://github.com/RitwikSaikia/jsonrpc 241 | HEAD 242 | 243 | 244 | 245 | 246 | ritwik-mvn-repo 247 | http://ritwik.net/mvn/releases/ 248 | 249 | true 250 | always 251 | warn 252 | 253 | 254 | false 255 | never 256 | fail 257 | 258 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/assembly/assembly-descriptor.xml: -------------------------------------------------------------------------------- 1 | 2 | release 3 | 4 | zip 5 | 6 | 7 | 8 | ../ 9 | 10 | 11 | LICENSE.txt 12 | 13 | 14 | 15 | target 16 | 17 | 18 | jsonrpc-*.jar 19 | 20 | 21 | 22 | target/apidocs 23 | docs/javadocs 24 | 25 | ** 26 | 27 | 28 | 29 | 30 | 31 | lib 32 | 33 | org.json.rpc:jsonrpc 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/client/HttpJsonRpcClientTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.client; 18 | 19 | import org.json.rpc.commons.JsonRpcClientException; 20 | 21 | import java.io.BufferedInputStream; 22 | import java.io.ByteArrayOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | import java.net.HttpURLConnection; 27 | import java.net.URL; 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.zip.GZIPInputStream; 31 | 32 | public class HttpJsonRpcClientTransport implements JsonRpcClientTransport { 33 | 34 | private URL url; 35 | private final Map headers; 36 | 37 | public HttpJsonRpcClientTransport(URL url) { 38 | this.url = url; 39 | this.headers = new HashMap(); 40 | } 41 | 42 | public final void setHeader(String key, String value) { 43 | this.headers.put(key, value); 44 | } 45 | 46 | public final String call(String requestData) throws Exception { 47 | String responseData = post(url, headers, requestData); 48 | return responseData; 49 | } 50 | 51 | private String post(URL url, Map headers, String data) 52 | throws IOException { 53 | 54 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 55 | 56 | if (headers != null) { 57 | for (Map.Entry entry : headers.entrySet()) { 58 | connection.addRequestProperty(entry.getKey(), entry.getValue()); 59 | } 60 | } 61 | 62 | connection.addRequestProperty("Accept-Encoding", "gzip"); 63 | 64 | connection.setRequestMethod("POST"); 65 | connection.setDoOutput(true); 66 | connection.connect(); 67 | 68 | OutputStream out = null; 69 | 70 | try { 71 | out = connection.getOutputStream(); 72 | 73 | out.write(data.getBytes()); 74 | out.flush(); 75 | out.close(); 76 | 77 | int statusCode = connection.getResponseCode(); 78 | if (statusCode != HttpURLConnection.HTTP_OK) { 79 | throw new JsonRpcClientException("unexpected status code returned : " + statusCode); 80 | } 81 | } finally { 82 | if (out != null) { 83 | out.close(); 84 | } 85 | } 86 | 87 | String responseEncoding = connection.getHeaderField("Content-Encoding"); 88 | responseEncoding = (responseEncoding == null ? "" : responseEncoding.trim()); 89 | 90 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 91 | 92 | InputStream in = connection.getInputStream(); 93 | try { 94 | in = connection.getInputStream(); 95 | if ("gzip".equalsIgnoreCase(responseEncoding)) { 96 | in = new GZIPInputStream(in); 97 | } 98 | in = new BufferedInputStream(in); 99 | 100 | byte[] buff = new byte[1024]; 101 | int n; 102 | while ((n = in.read(buff)) > 0) { 103 | bos.write(buff, 0, n); 104 | } 105 | bos.flush(); 106 | bos.close(); 107 | } finally { 108 | if (in != null) { 109 | in.close(); 110 | } 111 | } 112 | 113 | return bos.toString(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/client/JsonRpcClientTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.client; 18 | 19 | public interface JsonRpcClientTransport { 20 | 21 | String call(String requestData) throws Exception; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/client/JsonRpcInvoker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.client; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.JsonArray; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonObject; 23 | import com.google.gson.JsonParser; 24 | import org.json.rpc.commons.GsonTypeChecker; 25 | import org.json.rpc.commons.JsonRpcClientException; 26 | import org.json.rpc.commons.JsonRpcRemoteException; 27 | import org.json.rpc.commons.TypeChecker; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | import java.io.StringReader; 32 | import java.lang.reflect.InvocationHandler; 33 | import java.lang.reflect.Method; 34 | import java.lang.reflect.Proxy; 35 | import java.util.Random; 36 | 37 | public final class JsonRpcInvoker { 38 | 39 | private static final Logger LOG = LoggerFactory.getLogger(JsonRpcInvoker.class); 40 | 41 | private final Random rand = new Random(); 42 | 43 | private final TypeChecker typeChecker; 44 | 45 | private final Gson gson; 46 | 47 | public JsonRpcInvoker() { 48 | this(new GsonTypeChecker(), new Gson()); 49 | } 50 | 51 | public JsonRpcInvoker(Gson gson) { 52 | this(new GsonTypeChecker(), gson); 53 | } 54 | 55 | public JsonRpcInvoker(TypeChecker typeChecker) { 56 | this(typeChecker, new Gson()); 57 | } 58 | 59 | public JsonRpcInvoker(TypeChecker typeChecker, Gson gson) { 60 | this.typeChecker = typeChecker; 61 | this.gson = gson; 62 | } 63 | 64 | public T get(final JsonRpcClientTransport transport, final String handle, final Class... classes) { 65 | for (Class clazz : classes) { 66 | typeChecker.isValidInterface(clazz); 67 | } 68 | return (T) Proxy.newProxyInstance(JsonRpcInvoker.class.getClassLoader(), classes, new InvocationHandler() { 69 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 70 | return JsonRpcInvoker.this.invoke(handle, transport, method, args); 71 | } 72 | }); 73 | } 74 | 75 | private Object invoke(String handleName, 76 | JsonRpcClientTransport transport, Method method, 77 | Object[] args) throws Throwable { 78 | int id = rand.nextInt(Integer.MAX_VALUE); 79 | String methodName = handleName + "." + method.getName(); 80 | 81 | JsonObject req = new JsonObject(); 82 | req.addProperty("id", id); 83 | req.addProperty("method", methodName); 84 | 85 | JsonArray params = new JsonArray(); 86 | if (args != null) { 87 | for (Object o : args) { 88 | params.add(gson.toJsonTree(o)); 89 | } 90 | } 91 | req.add("params", params); 92 | 93 | String requestData = req.toString(); 94 | LOG.debug("JSON-RPC >> {}", requestData); 95 | String responseData; 96 | try { 97 | responseData = transport.call(requestData); 98 | } catch (Exception e) { 99 | throw new JsonRpcClientException("unable to get data from transport", e); 100 | } 101 | LOG.debug("JSON-RPC << {}", responseData); 102 | 103 | JsonParser parser = new JsonParser(); 104 | JsonObject resp = (JsonObject) parser.parse(new StringReader(responseData)); 105 | 106 | JsonElement result = resp.get("result"); 107 | JsonElement error = resp.get("error"); 108 | 109 | if (error != null && !error.isJsonNull()) { 110 | if (error.isJsonPrimitive()) { 111 | throw new JsonRpcRemoteException(error.getAsString()); 112 | } else if (error.isJsonObject()) { 113 | JsonObject o = error.getAsJsonObject(); 114 | Integer code = (o.has("code") ? o.get("code").getAsInt() : null); 115 | String message = (o.has("message") ? o.get("message").getAsString() : null); 116 | String data = (o.has("data") ? (o.get("data") instanceof JsonObject ? o.get("data").toString() : o.get("data").getAsString()) : null); 117 | throw new JsonRpcRemoteException(code, message, data); 118 | } else { 119 | throw new JsonRpcRemoteException("unknown error, data = " + error.toString()); 120 | } 121 | } 122 | 123 | if (method.getReturnType() == void.class) { 124 | return null; 125 | } 126 | 127 | return gson.fromJson(result.toString(), method.getReturnType()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/AllowAllTypeChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | public class AllowAllTypeChecker extends TypeChecker { 22 | 23 | public boolean isValidType(Class clazz) { 24 | return true; 25 | } 26 | 27 | public boolean isValidType(Class clazz, boolean throwException) { 28 | return true; 29 | } 30 | 31 | public String getTypeName(Class clazz) { 32 | return clazz.getName(); 33 | } 34 | 35 | public boolean isValidMethod(Method method) { 36 | return true; 37 | } 38 | 39 | public boolean isValidInterface(Class clazz) { 40 | return true; 41 | } 42 | 43 | public boolean isValidMethod(Method method, boolean throwException) { 44 | return true; 45 | } 46 | 47 | public boolean isValidInterface(Class clazz, boolean throwException) { 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/GsonTypeChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | import java.lang.reflect.Constructor; 20 | import java.lang.reflect.Field; 21 | import java.lang.reflect.Modifier; 22 | import java.util.Date; 23 | import java.util.HashSet; 24 | import java.util.Set; 25 | 26 | public class GsonTypeChecker extends TypeChecker { 27 | 28 | @Override 29 | public boolean isValidType(Class clazz, boolean throwException) { 30 | return isValidType(clazz, throwException, null); 31 | } 32 | 33 | private boolean isValidType(Class clazz, boolean throwException, Set> visited) { 34 | if (clazz.isPrimitive()) { 35 | return true; 36 | } 37 | 38 | if (Boolean.class == clazz) { 39 | return true; 40 | } 41 | 42 | if (Number.class.isAssignableFrom(clazz)) { 43 | return true; 44 | } 45 | 46 | if (String.class == clazz) { 47 | return true; 48 | } 49 | 50 | if (Character.class == clazz) { 51 | return true; 52 | } 53 | 54 | if (Date.class == clazz) { 55 | return true; 56 | } 57 | 58 | if (clazz.isArray()) { 59 | return this.isValidType(clazz.getComponentType(), throwException, visited); 60 | } 61 | 62 | /** 63 | * False cases 64 | */ 65 | 66 | if (clazz.isAnonymousClass()) { 67 | if (throwException) { 68 | throw new IllegalArgumentException("anonymous class not allowed : " + clazz); 69 | } 70 | return false; 71 | } 72 | 73 | if (Modifier.isInterface(clazz.getModifiers()) || Modifier.isAbstract(clazz.getModifiers())) { 74 | if (throwException) { 75 | throw new IllegalArgumentException("abstract class or interface not allowed : " + clazz); 76 | } 77 | return false; 78 | } 79 | 80 | if (clazz.getTypeParameters().length > 0) { 81 | if (throwException) { 82 | throw new IllegalArgumentException("parametrized classes not allowed : " + clazz); 83 | } 84 | return false; 85 | } 86 | 87 | boolean zeroArgConstructor = (clazz.getConstructors().length == 0); 88 | for (Constructor c : clazz.getConstructors()) { 89 | if (c.getParameterTypes().length == 0) { 90 | zeroArgConstructor = true; 91 | break; 92 | } 93 | } 94 | 95 | if (!zeroArgConstructor) { 96 | if (throwException) { 97 | throw new IllegalArgumentException("no zero-arg constructor found : " + clazz); 98 | } 99 | return false; 100 | } 101 | 102 | // avoid cyclic references 103 | // Issue #6: Be more lenient and allow more types, 104 | // let the developer handle StackOverFlowError 105 | // in case of cycles 106 | visited = (visited == null ? new HashSet>() : visited); 107 | if (visited.contains(clazz)) { 108 | return true; 109 | } 110 | visited.add(clazz); 111 | 112 | // Check for fields because Gson uses fields 113 | for (Field f : clazz.getDeclaredFields()) { 114 | int m = f.getModifiers(); 115 | if (Modifier.isStatic(m) || Modifier.isTransient(m)) { 116 | continue; 117 | } 118 | 119 | if (Modifier.isFinal(m)) { 120 | if (throwException) { 121 | throw new IllegalArgumentException("final field found : " + f); 122 | } 123 | return false; 124 | } 125 | 126 | boolean result = false; 127 | try { 128 | result = isValidType(f.getType(), throwException, visited); 129 | if (!result) { 130 | if (throwException) { 131 | throw new IllegalArgumentException("invalid field found : " + f); 132 | } 133 | return false; 134 | } 135 | } catch (RuntimeException e) { 136 | if (!result) { 137 | if (throwException) { 138 | throw new IllegalArgumentException("invalid field found : " + f, e); 139 | } 140 | return false; 141 | } 142 | } 143 | } 144 | 145 | 146 | return true; 147 | } 148 | 149 | @Override 150 | public String getTypeName(Class clazz) { 151 | if (clazz == void.class || clazz == Void.class) { 152 | return void.class.getName(); 153 | } 154 | 155 | if (clazz == boolean.class || Boolean.class == clazz) { 156 | return boolean.class.getName(); 157 | } 158 | 159 | if (clazz == double.class || clazz == float.class 160 | || Double.class == clazz || Float.class == clazz) { 161 | return double.class.getName(); 162 | } 163 | 164 | if (clazz == byte.class || clazz == char.class || clazz == int.class || clazz == short.class 165 | || clazz == long.class || clazz == Character.class || Number.class.isAssignableFrom(clazz)) { 166 | return int.class.getName(); 167 | } 168 | 169 | if (clazz == String.class) { 170 | return "string"; 171 | } 172 | 173 | if (clazz.isArray()) { 174 | return "array"; 175 | } 176 | 177 | return "struct"; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/JsonRpcClientException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | public class JsonRpcClientException extends JsonRpcException { 20 | public JsonRpcClientException(String message) { 21 | super(message); 22 | } 23 | 24 | public JsonRpcClientException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/JsonRpcErrorCodes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | public final class JsonRpcErrorCodes { 20 | 21 | public static final int PARSE_ERROR_CODE = -32700; 22 | public static final int INVALID_REQUEST_ERROR_CODE = -32600; 23 | public static final int METHOD_NOT_FOUND_ERROR_CODE = -32601; 24 | public static final int INVALID_PARAMS_ERROR_CODE = -32602; 25 | public static final int INTERNAL_ERROR_CODE = -32603; 26 | 27 | private static final int SERVER_ERROR_START = -32000; 28 | 29 | 30 | /** 31 | * Server error range : (-32099..-32000) 32 | */ 33 | 34 | public static int getServerError(int n) { 35 | return SERVER_ERROR_START - n; 36 | } 37 | 38 | private JsonRpcErrorCodes() { 39 | throw new AssertionError(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/JsonRpcException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | public class JsonRpcException extends RuntimeException { 20 | 21 | public JsonRpcException(String message) { 22 | super(message); 23 | } 24 | 25 | public JsonRpcException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/JsonRpcRemoteException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | public final class JsonRpcRemoteException extends JsonRpcException { 20 | 21 | private final Integer code; 22 | private final String msg; 23 | private final String data; 24 | 25 | public JsonRpcRemoteException(String msg) { 26 | super(msg); 27 | this.code = null; 28 | this.msg = msg; 29 | this.data = null; 30 | } 31 | 32 | public JsonRpcRemoteException(Integer code, String msg, String data) { 33 | super(format(code, msg, data)); 34 | this.code = code; 35 | this.msg = msg; 36 | this.data = data; 37 | } 38 | 39 | public Integer getCode() { 40 | return code; 41 | } 42 | 43 | public String getMsg() { 44 | return msg; 45 | } 46 | 47 | public String getData() { 48 | return data; 49 | } 50 | 51 | private static String format(Integer code, String message, String data) { 52 | StringBuilder str = new StringBuilder(); 53 | str.append("jsonrpc error"); 54 | if (code != null) { 55 | str.append("[").append(code).append("]"); 56 | } 57 | str.append(" : "); 58 | if (message != null) { 59 | str.append(message); 60 | } 61 | if (data != null) { 62 | str.append("\n"); 63 | str.append("Caused by " + data); 64 | } 65 | return str.toString(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/RpcIntroSpection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | public interface RpcIntroSpection { 20 | 21 | String[] listMethods(); 22 | 23 | String[] methodSignature(String method); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/commons/TypeChecker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | import java.lang.reflect.Method; 20 | import java.lang.reflect.Modifier; 21 | 22 | public abstract class TypeChecker { 23 | 24 | public boolean isValidType(Class clazz) { 25 | return isValidType(clazz, false); 26 | } 27 | 28 | public abstract boolean isValidType(Class clazz, boolean throwException); 29 | 30 | public abstract String getTypeName(Class clazz); 31 | 32 | public boolean isValidMethod(Method method) { 33 | return isValidMethod(method, false); 34 | } 35 | 36 | public boolean isValidInterface(Class clazz) { 37 | return isValidInterface(clazz, false); 38 | } 39 | 40 | public boolean isValidMethod(Method method, boolean throwException) { 41 | Class returnType = method.getReturnType(); 42 | boolean result = false; 43 | try { 44 | result = isValidType(returnType, throwException); 45 | if (!result) { 46 | if (throwException) { 47 | throw new IllegalArgumentException("invalid return type : " + returnType); 48 | } 49 | return false; 50 | } 51 | } catch (RuntimeException e) { 52 | if (!result) { 53 | if (throwException) { 54 | throw new IllegalArgumentException("invalid return type : " + returnType, e); 55 | } 56 | return false; 57 | } 58 | } 59 | 60 | for (Class paramType : method.getParameterTypes()) { 61 | result = false; 62 | try { 63 | result = isValidType(paramType, throwException); 64 | if (!result) { 65 | if (throwException) { 66 | throw new IllegalArgumentException("invalid parameter type : " + paramType); 67 | } 68 | return false; 69 | } 70 | } catch (RuntimeException e) { 71 | if (!result) { 72 | if (throwException) { 73 | throw new IllegalArgumentException("invalid parameter type : " + paramType, e); 74 | } 75 | return false; 76 | } 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | 83 | public boolean isValidInterface(Class clazz, boolean throwException) { 84 | if (!clazz.isInterface()) { 85 | if (throwException) { 86 | throw new IllegalArgumentException("not an interface : " + clazz); 87 | } 88 | return false; 89 | } 90 | 91 | for (Method method : clazz.getDeclaredMethods()) { 92 | int m = method.getModifiers(); 93 | if (Modifier.isStatic(m)) { 94 | continue; 95 | } 96 | 97 | boolean result = false; 98 | try { 99 | result = isValidMethod(method, throwException); 100 | if (!result) { 101 | if (throwException) { 102 | throw new IllegalArgumentException("invalid method : " + method); 103 | } 104 | return false; 105 | } 106 | } catch (RuntimeException e) { 107 | if (!result) { 108 | if (throwException) { 109 | throw new IllegalArgumentException("invalid method : " + method, e); 110 | } 111 | return false; 112 | } 113 | } 114 | } 115 | 116 | return true; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/server/HandleEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.server; 18 | 19 | import org.json.rpc.commons.TypeChecker; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.HashMap; 25 | import java.util.HashSet; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Set; 29 | import java.util.TreeMap; 30 | 31 | class HandleEntry { 32 | 33 | private final T handler; 34 | private final Map signatures; 35 | private final Set methods; 36 | 37 | public HandleEntry(TypeChecker typeChecker, T handler, Class... classes) { 38 | if (handler == null) { 39 | throw new NullPointerException("handler"); 40 | } 41 | 42 | if (classes.length == 0) { 43 | throw new IllegalArgumentException( 44 | "at least one interface has to be mentioned"); 45 | } 46 | 47 | this.handler = handler; 48 | 49 | Map> map = new HashMap>(); 50 | Set set = new HashSet(); 51 | 52 | for (Class clazz : classes) { 53 | typeChecker.isValidInterface(clazz, true); 54 | 55 | if (!clazz.isInterface()) { 56 | throw new IllegalArgumentException( 57 | "class should be an interface : " + clazz); 58 | } 59 | 60 | for (Method m : clazz.getMethods()) { 61 | set.add(m); 62 | Class[] params = m.getParameterTypes(); 63 | 64 | List list = map.get(m.getName()); 65 | if (list == null) { 66 | list = new ArrayList(); 67 | } 68 | StringBuffer buff = new StringBuffer(typeChecker.getTypeName(m 69 | .getReturnType())); 70 | for (int i = 0; i < params.length; i++) { 71 | buff.append(",").append(typeChecker.getTypeName(params[i])); 72 | } 73 | list.add(buff.toString()); 74 | map.put(m.getName(), list); 75 | } 76 | 77 | } 78 | 79 | Map signs = new TreeMap(); 80 | for (Map.Entry> e : map.entrySet()) { 81 | String[] arr = new String[e.getValue().size()]; 82 | signs.put(e.getKey(), e.getValue().toArray(arr)); 83 | } 84 | 85 | this.methods = Collections.unmodifiableSet(set); 86 | this.signatures = Collections.unmodifiableMap(signs); 87 | } 88 | 89 | public T getHandler() { 90 | return handler; 91 | } 92 | 93 | public java.util.Map getSignatures() { 94 | return signatures; 95 | } 96 | 97 | public java.util.Set getMethods() { 98 | return methods; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/server/JsonRpcExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.server; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.JsonArray; 21 | import com.google.gson.JsonElement; 22 | import com.google.gson.JsonObject; 23 | import com.google.gson.JsonParser; 24 | import org.json.rpc.commons.GsonTypeChecker; 25 | import org.json.rpc.commons.JsonRpcErrorCodes; 26 | import org.json.rpc.commons.JsonRpcException; 27 | import org.json.rpc.commons.JsonRpcRemoteException; 28 | import org.json.rpc.commons.RpcIntroSpection; 29 | import org.json.rpc.commons.TypeChecker; 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | import java.io.PrintWriter; 34 | import java.io.StringReader; 35 | import java.io.StringWriter; 36 | import java.lang.reflect.InvocationTargetException; 37 | import java.lang.reflect.Method; 38 | import java.util.ArrayList; 39 | import java.util.HashMap; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Set; 43 | import java.util.TreeSet; 44 | import java.util.regex.Matcher; 45 | import java.util.regex.Pattern; 46 | 47 | 48 | public final class JsonRpcExecutor implements RpcIntroSpection { 49 | 50 | private static final Logger LOG = LoggerFactory.getLogger(JsonRpcExecutor.class); 51 | 52 | private static final Pattern METHOD_PATTERN = Pattern 53 | .compile("([_a-zA-Z][_a-zA-Z0-9]*)\\.([_a-zA-Z][_a-zA-Z0-9]*)"); 54 | 55 | private final Map> handlers; 56 | 57 | private final TypeChecker typeChecker; 58 | private volatile boolean locked; 59 | 60 | private final Gson gson; 61 | 62 | public JsonRpcExecutor() { 63 | this(new GsonTypeChecker(), new Gson()); 64 | } 65 | 66 | public JsonRpcExecutor(Gson gson) { 67 | this(new GsonTypeChecker(), gson); 68 | } 69 | 70 | public JsonRpcExecutor(TypeChecker typeChecker) { 71 | this(typeChecker, new Gson()); 72 | } 73 | 74 | @SuppressWarnings("unchecked") 75 | public JsonRpcExecutor(TypeChecker typeChecker, Gson gson) { 76 | this.typeChecker = typeChecker; 77 | this.gson = gson; 78 | this.handlers = new HashMap>(); 79 | addHandler("system", this, RpcIntroSpection.class); 80 | } 81 | 82 | public boolean isLocked() { 83 | return locked; 84 | } 85 | 86 | public void addHandler(String name, T handler, Class... classes) { 87 | if (locked) { 88 | throw new JsonRpcException("executor has been locked, can't add more handlers"); 89 | } 90 | 91 | synchronized (handlers) { 92 | HandleEntry handleEntry = new HandleEntry(typeChecker, handler, classes); 93 | if (this.handlers.containsKey(name)) { 94 | throw new IllegalArgumentException("handler already exists"); 95 | } 96 | this.handlers.put(name, handleEntry); 97 | } 98 | } 99 | 100 | public void execute(JsonRpcServerTransport transport) { 101 | if (!locked) { 102 | synchronized (handlers) { 103 | locked = true; 104 | } 105 | LOG.info("locking executor to avoid modification"); 106 | } 107 | 108 | String methodName = null; 109 | JsonArray params = null; 110 | 111 | JsonObject resp = new JsonObject(); 112 | resp.addProperty("jsonrpc", "2.0"); 113 | 114 | String errorMessage = null; 115 | Integer errorCode = null; 116 | String errorData = null; 117 | 118 | JsonObject req = null; 119 | try { 120 | String requestData = transport.readRequest(); 121 | LOG.debug("JSON-RPC >> {}", requestData); 122 | JsonParser parser = new JsonParser(); 123 | req = (JsonObject) parser.parse(new StringReader(requestData)); 124 | } catch (Throwable t) { 125 | errorCode = JsonRpcErrorCodes.PARSE_ERROR_CODE; 126 | errorMessage = "unable to parse json-rpc request"; 127 | errorData = getStackTrace(t); 128 | 129 | LOG.warn(errorMessage, t); 130 | 131 | sendError(transport, resp, errorCode, errorMessage, errorData); 132 | return; 133 | } 134 | 135 | 136 | try { 137 | assert req != null; 138 | resp.add("id", req.get("id")); 139 | 140 | methodName = req.getAsJsonPrimitive("method").getAsString(); 141 | params = (JsonArray) req.get("params"); 142 | if (params == null) { 143 | params = new JsonArray(); 144 | } 145 | } catch (Throwable t) { 146 | errorCode = JsonRpcErrorCodes.INVALID_REQUEST_ERROR_CODE; 147 | errorMessage = "unable to read request"; 148 | errorData = getStackTrace(t); 149 | 150 | 151 | LOG.warn(errorMessage, t); 152 | sendError(transport, resp, errorCode, errorMessage, errorData); 153 | return; 154 | } 155 | 156 | try { 157 | JsonElement result = executeMethod(methodName, params); 158 | resp.add("result", result); 159 | } catch (Throwable t) { 160 | LOG.warn("exception occured while executing : " + methodName, t); 161 | if (t instanceof JsonRpcRemoteException) { 162 | sendError(transport, resp, (JsonRpcRemoteException) t); 163 | return; 164 | } 165 | errorCode = JsonRpcErrorCodes.getServerError(1); 166 | errorMessage = t.getMessage(); 167 | errorData = getStackTrace(t); 168 | sendError(transport, resp, errorCode, errorMessage, errorData); 169 | return; 170 | } 171 | 172 | try { 173 | String responseData = resp.toString(); 174 | LOG.debug("JSON-RPC result << {}", responseData); 175 | transport.writeResponse(responseData); 176 | } catch (Exception e) { 177 | LOG.warn("unable to write response : " + resp, e); 178 | } 179 | } 180 | 181 | private void sendError(JsonRpcServerTransport transport, JsonObject resp, JsonRpcRemoteException e) { 182 | sendError(transport, resp, e.getCode(), e.getMessage(), e.getData()); 183 | } 184 | 185 | private void sendError(JsonRpcServerTransport transport, JsonObject resp, Integer code, String message, String data) { 186 | JsonObject error = new JsonObject(); 187 | if (code != null) { 188 | error.addProperty("code", code); 189 | } 190 | 191 | if (message != null) { 192 | error.addProperty("message", message); 193 | } 194 | 195 | if (data != null) { 196 | error.addProperty("data", data); 197 | } 198 | 199 | resp.add("error", error); 200 | resp.remove("result"); 201 | String responseData = resp.toString(); 202 | 203 | LOG.debug("JSON-RPC error << {}", responseData); 204 | try { 205 | transport.writeResponse(responseData); 206 | } catch (Exception e) { 207 | LOG.error("unable to write error response : " + responseData, e); 208 | } 209 | } 210 | 211 | private String getStackTrace(Throwable t) { 212 | StringWriter str = new StringWriter(); 213 | PrintWriter w = new PrintWriter(str); 214 | t.printStackTrace(w); 215 | w.close(); 216 | return str.toString(); 217 | } 218 | 219 | private JsonElement executeMethod(String methodName, JsonArray params) throws Throwable { 220 | try { 221 | Matcher mat = METHOD_PATTERN.matcher(methodName); 222 | if (!mat.find()) { 223 | throw new JsonRpcRemoteException(JsonRpcErrorCodes.INVALID_REQUEST_ERROR_CODE, "invalid method name", null); 224 | } 225 | 226 | String handleName = mat.group(1); 227 | methodName = mat.group(2); 228 | 229 | HandleEntry handleEntry = handlers.get(handleName); 230 | if (handleEntry == null) { 231 | throw new JsonRpcRemoteException(JsonRpcErrorCodes.METHOD_NOT_FOUND_ERROR_CODE, "no such method exists", null); 232 | } 233 | 234 | Method executableMethod = null; 235 | for (Method m : handleEntry.getMethods()) { 236 | if (!m.getName().equals(methodName)) { 237 | continue; 238 | } 239 | 240 | if (canExecute(m, params)) { 241 | executableMethod = m; 242 | break; 243 | } 244 | } 245 | 246 | if (executableMethod == null) { 247 | throw new JsonRpcRemoteException(JsonRpcErrorCodes.METHOD_NOT_FOUND_ERROR_CODE, "no such method exists", null); 248 | } 249 | 250 | Object result = executableMethod.invoke( 251 | handleEntry.getHandler(), getParameters(executableMethod, params)); 252 | 253 | return gson.toJsonTree(result); 254 | } catch (Throwable t) { 255 | if (t instanceof InvocationTargetException) { 256 | t = ((InvocationTargetException) t).getTargetException(); 257 | } 258 | if (t instanceof JsonRpcRemoteException) { 259 | throw (JsonRpcRemoteException) t; 260 | } 261 | throw new JsonRpcRemoteException(JsonRpcErrorCodes.getServerError(0), t.getMessage(), getStackTrace(t)); 262 | } 263 | } 264 | 265 | public boolean canExecute(Method method, JsonArray params) { 266 | if (method.getParameterTypes().length != params.size()) { 267 | return false; 268 | } 269 | 270 | return true; 271 | } 272 | 273 | public Object[] getParameters(Method method, JsonArray params) { 274 | List list = new ArrayList(); 275 | Class[] types = method.getParameterTypes(); 276 | for (int i = 0; i < types.length; i++) { 277 | JsonElement p = params.get(i); 278 | Object o = gson.fromJson(p.toString(), types[i]); 279 | list.add(o); 280 | } 281 | return list.toArray(); 282 | } 283 | 284 | public String[] listMethods() { 285 | Set methods = new TreeSet(); 286 | for (String name : this.handlers.keySet()) { 287 | HandleEntry handleEntry = this.handlers.get(name); 288 | for (String method : handleEntry.getSignatures().keySet()) { 289 | methods.add(name + "." + method); 290 | } 291 | } 292 | String[] arr = new String[methods.size()]; 293 | return methods.toArray(arr); 294 | } 295 | 296 | public String[] methodSignature(String method) { 297 | if (method == null) { 298 | throw new NullPointerException("method"); 299 | } 300 | 301 | Matcher mat = METHOD_PATTERN.matcher(method); 302 | if (!mat.find()) { 303 | throw new IllegalArgumentException("invalid method name"); 304 | } 305 | 306 | String handleName = mat.group(1); 307 | String methodName = mat.group(2); 308 | 309 | Set signatures = new TreeSet(); 310 | 311 | HandleEntry handleEntry = handlers.get(handleName); 312 | if (handleEntry == null) { 313 | throw new IllegalArgumentException("no such method exists"); 314 | } 315 | 316 | for (Method m : handleEntry.getMethods()) { 317 | if (!m.getName().equals(methodName)) { 318 | continue; 319 | } 320 | 321 | String[] sign = handleEntry.getSignatures().get(m.getName()); 322 | 323 | StringBuffer buff = new StringBuffer(sign[0]); 324 | for (int i = 1; i < sign.length; i++) { 325 | buff.append(",").append(sign[i]); 326 | } 327 | 328 | signatures.add(buff.toString()); 329 | } 330 | 331 | if (signatures.size() == 0) { 332 | throw new IllegalArgumentException("no such method exists"); 333 | } 334 | 335 | String[] arr = new String[signatures.size()]; 336 | return signatures.toArray(arr); 337 | } 338 | 339 | 340 | } 341 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/server/JsonRpcServerTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.server; 18 | 19 | public interface JsonRpcServerTransport { 20 | 21 | String readRequest() throws Exception; 22 | 23 | void writeResponse(String responseData) throws Exception; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /jsonrpc-java/src/main/java/org/json/rpc/server/JsonRpcServletTransport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.server; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.InputStream; 23 | import java.io.PrintWriter; 24 | 25 | public class JsonRpcServletTransport implements JsonRpcServerTransport { 26 | 27 | private static final int BUFF_LENGTH = 1024; 28 | 29 | private final HttpServletRequest req; 30 | private final HttpServletResponse resp; 31 | 32 | 33 | public JsonRpcServletTransport(HttpServletRequest req, HttpServletResponse resp) { 34 | this.req = req; 35 | this.resp = resp; 36 | } 37 | 38 | public String readRequest() throws Exception { 39 | InputStream in = null; 40 | try { 41 | in = req.getInputStream(); 42 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 43 | 44 | byte[] buff = new byte[BUFF_LENGTH]; 45 | int n; 46 | while ((n = in.read(buff)) > 0) { 47 | bos.write(buff, 0, n); 48 | } 49 | 50 | return bos.toString(); 51 | } finally { 52 | if (in != null) { 53 | in.close(); 54 | } 55 | } 56 | } 57 | 58 | public void writeResponse(String responseData) throws Exception { 59 | byte[] data = responseData.getBytes(resp.getCharacterEncoding()); 60 | resp.addHeader("Content-Type", "application/json"); 61 | resp.setHeader("Content-Length", Integer.toString(data.length)); 62 | 63 | PrintWriter out = null; 64 | try { 65 | out = resp.getWriter(); 66 | out.write(responseData); 67 | out.flush(); 68 | } finally { 69 | if (out != null) { 70 | out.close(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /jsonrpc-java/src/test/java/org/json/rpc/JsonRpcTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc; 18 | 19 | import org.json.rpc.client.HttpJsonRpcClientTransport; 20 | import org.json.rpc.client.JsonRpcClientTransport; 21 | import org.json.rpc.client.JsonRpcInvoker; 22 | import org.json.rpc.commons.RpcIntroSpection; 23 | import org.json.rpc.server.JsonRpcExecutor; 24 | import org.json.rpc.server.JsonRpcServerTransport; 25 | import org.testng.annotations.AfterTest; 26 | import org.testng.annotations.BeforeTest; 27 | import org.testng.annotations.Test; 28 | 29 | import java.net.URL; 30 | import java.util.Arrays; 31 | 32 | import static org.testng.Assert.assertEquals; 33 | 34 | public class JsonRpcTest { 35 | 36 | private JsonRpcInvoker invoker; 37 | private JsonRpcExecutor executor; 38 | private RpcIntroSpection system; 39 | 40 | @BeforeTest 41 | public void setupTest() { 42 | invoker = new JsonRpcInvoker(); 43 | executor = new JsonRpcExecutor(); 44 | system = getInstance("system", RpcIntroSpection.class); 45 | } 46 | 47 | @AfterTest 48 | public void teardownTest() { 49 | invoker = null; 50 | executor = null; 51 | system = null; 52 | } 53 | 54 | @Test 55 | public void testListMethods() throws Exception { 56 | String[] methods = system.listMethods(); 57 | assertEquals(methods, new String[]{"system.listMethods", "system.methodSignature"}); 58 | } 59 | 60 | @Test 61 | public void testMethodSignature() throws Exception { 62 | String[] sig = system.methodSignature("system.listMethods"); 63 | assertEquals(sig, new String[]{"array"}); 64 | } 65 | 66 | 67 | private T getInstance(String handleName, Class... classes) { 68 | return invoker.get(new JsonRpcClientTransport() { 69 | public String call(final String requestData) throws Exception { 70 | final StringBuilder resultData = new StringBuilder(); 71 | JsonRpcServerTransport serverTransport = new JsonRpcServerTransport() { 72 | 73 | public String readRequest() throws Exception { 74 | return requestData; 75 | } 76 | 77 | public void writeResponse(String responseData) throws Exception { 78 | resultData.append(responseData); 79 | } 80 | }; 81 | executor.execute(serverTransport); 82 | return resultData.toString(); 83 | } 84 | }, handleName, classes); 85 | } 86 | 87 | //@Test 88 | public void testRemote() throws Exception { 89 | String url = "http://127.0.0.1:8888/rpc"; 90 | 91 | HttpJsonRpcClientTransport transport = new HttpJsonRpcClientTransport(new URL(url)); 92 | 93 | JsonRpcInvoker invoker = new JsonRpcInvoker(); 94 | 95 | RpcIntroSpection system = invoker.get(transport, "system", RpcIntroSpection.class); 96 | System.out.println(Arrays.toString(system.listMethods())); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /jsonrpc-java/src/test/java/org/json/rpc/client/JsonRpcInvokerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.client; 18 | 19 | import com.google.gson.JsonArray; 20 | import com.google.gson.JsonElement; 21 | import com.google.gson.JsonObject; 22 | import com.google.gson.JsonParser; 23 | import org.json.rpc.commons.JsonRpcRemoteException; 24 | import org.testng.annotations.BeforeTest; 25 | import org.testng.annotations.DataProvider; 26 | import org.testng.annotations.Test; 27 | 28 | import static org.testng.Assert.assertEquals; 29 | import static org.testng.Assert.assertNull; 30 | import static org.testng.Assert.fail; 31 | 32 | public class JsonRpcInvokerTest { 33 | 34 | private JsonRpcInvoker invoker; 35 | 36 | @BeforeTest 37 | public void setup() { 38 | invoker = new JsonRpcInvoker(); 39 | } 40 | 41 | @Test 42 | public void testErrorWithPrimitiveData() { 43 | final String errorMessage = "some error message"; 44 | JsonObject resp = new JsonObject(); 45 | resp.addProperty("jsonrpc", "2.0"); 46 | resp.addProperty("error", errorMessage); 47 | 48 | TestInterface handle = invoker.get(getTransport(resp), "someHandler", TestInterface.class); 49 | 50 | try { 51 | handle.call(1); 52 | fail("should throw exception"); 53 | } catch (JsonRpcRemoteException e) { 54 | assertNull(e.getCode()); 55 | assertEquals(e.getMsg(), errorMessage); 56 | assertNull(e.getData()); 57 | } 58 | } 59 | 60 | @Test // issue number is from google code, 61 | // upgrade to Gson 2.2.2 was breaking it, as 62 | // previous version was not able to detect the 63 | // last '}' brace as parse error 64 | public void testIssue0002() throws Exception { 65 | JsonObject resp = new JsonObject(); 66 | resp.addProperty("jsonrpc", "2.0"); 67 | JsonObject error = new JsonObject(); 68 | error.addProperty("code", -32002); 69 | error.addProperty("message", "service.invalid-parameters"); 70 | error.add("data", new JsonParser().parse("{\"email\":[\"'email' is no valid email address in the basic format local-part@hostname\"]}")); 71 | resp.add("error", error); 72 | 73 | TestInterface handle = invoker.get(getTransport(resp), "someHandler", TestInterface.class); 74 | 75 | try { 76 | handle.call(1); 77 | } catch (UnsupportedOperationException e) { 78 | e.printStackTrace(); 79 | fail("issue 0002 is not resolved"); 80 | } catch (Exception e) { 81 | // ignore 82 | } 83 | } 84 | 85 | @DataProvider 86 | public Object[][] testErrorData() { 87 | return new Object[][]{ 88 | {null, null, null}, // 89 | {123, null, null}, // 90 | {null, "some message", null}, // 91 | {null, null, "some data"}, // 92 | {null, "some message", "some data"}, // 93 | {123, null, "some data"}, // 94 | {123, "some message", null}, // 95 | {123, "some message", "some data"}, // 96 | }; 97 | } 98 | 99 | @Test(dataProvider = "testErrorData") 100 | public void testError(Integer errorCode, String errorMessage, String errorData) { 101 | JsonObject resp = new JsonObject(); 102 | resp.addProperty("jsonrpc", "2.0"); 103 | 104 | JsonObject error = new JsonObject(); 105 | if (errorCode != null) { 106 | error.addProperty("code", errorCode); 107 | } 108 | if (errorMessage != null) { 109 | error.addProperty("message", errorMessage); 110 | } 111 | if (errorData != null) { 112 | error.addProperty("data", errorData); 113 | } 114 | 115 | resp.add("error", error); 116 | 117 | 118 | TestInterface handle = invoker.get(getTransport(resp), "someHandler", TestInterface.class); 119 | 120 | try { 121 | handle.call(1); 122 | fail("should throw exception"); 123 | } catch (JsonRpcRemoteException e) { 124 | if (errorCode == null) { 125 | assertNull(errorCode); 126 | } else { 127 | assertEquals(e.getCode(), errorCode); 128 | } 129 | if (errorMessage == null) { 130 | assertNull(e.getMsg()); 131 | } else { 132 | assertEquals(e.getMsg(), errorMessage); 133 | } 134 | if (errorData == null) { 135 | assertNull(e.getData()); 136 | } else { 137 | assertEquals(e.getData(), errorData); 138 | } 139 | } 140 | } 141 | 142 | @Test 143 | public void testErrorArray() { 144 | JsonObject resp = new JsonObject(); 145 | resp.addProperty("jsonrpc", "2.0"); 146 | 147 | JsonArray error = new JsonArray(); 148 | resp.add("error", error); 149 | 150 | TestInterface handle = invoker.get(getTransport(resp), "someHandler", TestInterface.class); 151 | 152 | try { 153 | handle.call(1); 154 | fail("should throw exception"); 155 | } catch (JsonRpcRemoteException e) { 156 | assertNull(e.getCode()); 157 | assertNull(e.getData()); 158 | } 159 | } 160 | 161 | 162 | @Test 163 | public void testResultVoid() { 164 | JsonObject resp = new JsonObject(); 165 | resp.addProperty("jsonrpc", "2.0"); 166 | 167 | TestInterface handle = invoker.get(getTransport(resp), "someHandler", TestInterface.class); 168 | handle.call(); 169 | } 170 | 171 | 172 | static interface TestInterface { 173 | boolean call(int arg); 174 | 175 | void call(); 176 | } 177 | 178 | static JsonRpcClientTransport getTransport(final JsonElement resp) { 179 | return new JsonRpcClientTransport() { 180 | public String call(String requestData) throws Exception { 181 | return resp.toString(); 182 | } 183 | }; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /jsonrpc-java/src/test/java/org/json/rpc/commons/GsonTypeCheckerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.json.rpc.commons; 18 | 19 | import org.testng.annotations.BeforeTest; 20 | import org.testng.annotations.DataProvider; 21 | import org.testng.annotations.Test; 22 | 23 | import java.util.Date; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | import static org.testng.Assert.assertEquals; 29 | import static org.testng.Assert.assertFalse; 30 | import static org.testng.Assert.assertTrue; 31 | 32 | public class GsonTypeCheckerTest { 33 | 34 | private GsonTypeChecker typeChecker; 35 | 36 | @BeforeTest 37 | public void setup() { 38 | typeChecker = new GsonTypeChecker(); 39 | } 40 | 41 | @DataProvider 42 | public Object[][] validTypes() { 43 | return new Object[][]{ 44 | {void.class}, // 45 | {byte.class}, // 46 | {char.class}, // 47 | {int.class}, // 48 | {short.class}, // 49 | {long.class}, // 50 | {float.class}, // 51 | {double.class}, // 52 | {boolean.class}, // 53 | {Void.class}, // 54 | {String.class}, // 55 | {Byte.class}, // 56 | {Integer.class}, // 57 | {Short.class}, // 58 | {Long.class}, // 59 | {Float.class}, // 60 | {Double.class}, // 61 | {Boolean.class}, // 62 | {Character.class}, // 63 | {Date.class}, // 64 | 65 | {byte[].class}, // 66 | {char[].class}, // 67 | {int[].class}, // 68 | {short[].class}, // 69 | {long[].class}, // 70 | {float[].class}, // 71 | {double[].class}, // 72 | {boolean[].class}, // 73 | {String[].class}, // 74 | {Byte[].class}, // 75 | {Integer[].class}, // 76 | {Short[].class}, // 77 | {Long[].class}, // 78 | {Float[].class}, // 79 | {Double[].class}, // 80 | {Boolean[].class}, // 81 | {Character[].class}, // 82 | {Date[].class}, // 83 | 84 | 85 | {ValidInnerClass.class}, // 86 | {TooManyInnerClasses.class}, // 87 | {ZeroArgConstructorClass.class}, // 88 | {CyclicClass.class}, // 89 | 90 | {ValidInnerClass[].class}, // 91 | {TooManyInnerClasses[].class}, // 92 | {ZeroArgConstructorClass[].class}, // 93 | {CyclicClass[].class}, // 94 | }; 95 | } 96 | 97 | static class TooManyInnerClasses { 98 | A a; 99 | int b; 100 | 101 | static class A { 102 | C c; 103 | double d; 104 | 105 | static class C { 106 | String e; 107 | } 108 | } 109 | } 110 | 111 | static class ValidInnerClass { 112 | static ParameterizedClass a; 113 | transient CyclicClass b; 114 | 115 | int c; 116 | } 117 | 118 | static class ZeroArgConstructorClass { 119 | public ZeroArgConstructorClass() { 120 | } 121 | 122 | public ZeroArgConstructorClass(String a) { 123 | } 124 | } 125 | 126 | static class ParameterizedClass { 127 | String a; 128 | double b; 129 | } 130 | 131 | static class CyclicClass { 132 | CyclicClass a; 133 | int b; 134 | boolean c; 135 | } 136 | 137 | static class FinalFields { 138 | final int n = 0; 139 | } 140 | 141 | static class ParameterizedCollections { 142 | Set a; 143 | List b; 144 | Map d; 145 | } 146 | 147 | static interface InterfaceClass { 148 | } 149 | 150 | static abstract class AbstractClass { 151 | } 152 | 153 | static class NonZeroArgConstructorClass { 154 | public NonZeroArgConstructorClass(String c) { 155 | } 156 | 157 | public NonZeroArgConstructorClass(int a, double b) { 158 | } 159 | } 160 | 161 | 162 | @Test(dataProvider = "validTypes") 163 | public void testValidType(Class clazz) { 164 | assertTrue(typeChecker.isValidType(clazz)); 165 | } 166 | 167 | @Test(dataProvider = "validTypes") 168 | public void testValidTypeWithException(Class clazz) { 169 | assertTrue(typeChecker.isValidType(clazz, true)); 170 | } 171 | 172 | @DataProvider 173 | public Object[][] invalidTypes() { 174 | return new Object[][]{ 175 | {ParameterizedClass.class}, // 176 | {FinalFields.class}, // 177 | {ParameterizedCollections.class}, // 178 | {InterfaceClass.class}, // 179 | {AbstractClass.class}, // 180 | {NonZeroArgConstructorClass.class}, // 181 | 182 | {ParameterizedClass[].class}, // 183 | {FinalFields[].class}, // 184 | {ParameterizedCollections[].class}, // 185 | {InterfaceClass[].class}, // 186 | {AbstractClass[].class}, // 187 | {NonZeroArgConstructorClass[].class}, // 188 | 189 | {new Object() { 190 | }.getClass()}, // Anonymous class 191 | }; 192 | } 193 | 194 | 195 | @Test(dataProvider = "invalidTypes") 196 | public void testInvalidType(Class clazz) { 197 | assertFalse(typeChecker.isValidType(clazz)); 198 | } 199 | 200 | @Test(dataProvider = "invalidTypes", expectedExceptions = {IllegalArgumentException.class}) 201 | public void testInvalidTypeWithException(Class clazz) { 202 | assertFalse(typeChecker.isValidType(clazz, true)); 203 | } 204 | 205 | @DataProvider 206 | public Object[][] typeNames() { 207 | return new Object[][]{ 208 | {void.class, "void"}, // 209 | {Void.class, "void"}, // 210 | {boolean.class, "boolean"}, // 211 | {Boolean.class, "boolean"}, // 212 | {double.class, "double"}, // 213 | {Double.class, "double"}, // 214 | {float.class, "double"}, // 215 | {Float.class, "double"}, // 216 | {byte.class, "int"}, // 217 | {Byte.class, "int"}, // 218 | {char.class, "int"}, // 219 | {Character.class, "int"}, // 220 | {int.class, "int"}, // 221 | {Integer.class, "int"}, // 222 | {short.class, "int"}, // 223 | {Short.class, "int"}, // 224 | {long.class, "int"}, // 225 | {Long.class, "int"}, // 226 | {String.class, "string"}, // 227 | 228 | {boolean[].class, "array"}, // 229 | {Boolean[].class, "array"}, // 230 | {double[].class, "array"}, // 231 | {Double[].class, "array"}, // 232 | {float[].class, "array"}, // 233 | {Float[].class, "array"}, // 234 | {byte[].class, "array"}, // 235 | {Byte[].class, "array"}, // 236 | {char[].class, "array"}, // 237 | {Character[].class, "array"}, // 238 | {int[].class, "array"}, // 239 | {Integer[].class, "array"}, // 240 | {short[].class, "array"}, // 241 | {Short[].class, "array"}, // 242 | {long[].class, "array"}, // 243 | {Long[].class, "array"}, // 244 | {String[].class, "array"}, // 245 | {FinalFields[].class, "array"}, // 246 | 247 | {Set.class, "struct"}, // 248 | {CyclicClass.class, "struct"}, // 249 | {new Object() { 250 | }.getClass(), "struct"}, // 251 | }; 252 | } 253 | 254 | @Test(dataProvider = "typeNames") 255 | public void testGetTypeName(Class clazz, String name) { 256 | assertEquals(typeChecker.getTypeName(clazz), name); 257 | } 258 | 259 | 260 | } 261 | -------------------------------------------------------------------------------- /jsonrpc-java/src/test/java/org/json/rpc/server/CyclicReferenceBugImpl.java: -------------------------------------------------------------------------------- 1 | package org.json.rpc.server; 2 | 3 | public class CyclicReferenceBugImpl implements CyclicReferenceBug { 4 | 5 | public CyclicResult getResult() { 6 | return new CyclicResult(); 7 | } 8 | } 9 | 10 | interface CyclicReferenceBug { 11 | CyclicResult getResult(); 12 | } 13 | 14 | class CyclicResult { 15 | CyclicA a; 16 | CyclicA b; 17 | 18 | public CyclicResult() { 19 | } 20 | } 21 | 22 | class CyclicA { 23 | int value; 24 | CyclicA ref; 25 | 26 | public CyclicA() { 27 | } 28 | } -------------------------------------------------------------------------------- /jsonrpc-java/src/test/java/org/json/rpc/server/JsonRpcExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.json.rpc.server; 2 | 3 | import org.json.rpc.client.JsonRpcClientTransport; 4 | import org.json.rpc.client.JsonRpcInvoker; 5 | import org.json.rpc.commons.JsonRpcRemoteException; 6 | import org.testng.annotations.BeforeMethod; 7 | import org.testng.annotations.Test; 8 | 9 | import static org.testng.Assert.assertEquals; 10 | import static org.testng.Assert.assertNotNull; 11 | import static org.testng.Assert.assertTrue; 12 | import static org.testng.Assert.fail; 13 | import static org.testng.AssertJUnit.assertNull; 14 | 15 | /** 16 | * @author ritwik 17 | */ 18 | public class JsonRpcExecutorTest { 19 | 20 | private JsonRpcExecutor executor; 21 | 22 | @BeforeMethod 23 | public void setupMethod() { 24 | executor = new JsonRpcExecutor(); 25 | } 26 | 27 | @Test //Issue #6: Cyclic Reference if Custom Type is used more than once 28 | public void testIssue006_withDistinctEndPoint() throws Exception { 29 | CyclicReferenceBugImpl impl = new CyclicReferenceBugImpl() { 30 | 31 | public CyclicResult getResult() { 32 | CyclicA a = new CyclicA(); 33 | a.value = 1; 34 | CyclicA b = new CyclicA(); 35 | b.value = 2; 36 | CyclicResult result = new CyclicResult(); 37 | result.a = a; 38 | result.b = b; 39 | return result; 40 | } 41 | }; 42 | testCycle(impl, 1, 2, null, null); 43 | } 44 | 45 | @Test //Issue #6: Cyclic Reference if Custom Type is used more than once 46 | public void testIssue006_withSingleEndPoint() throws Exception { 47 | CyclicReferenceBugImpl impl = new CyclicReferenceBugImpl() { 48 | public CyclicResult getResult() { 49 | CyclicA a = new CyclicA(); 50 | a.value = 1; 51 | CyclicA b = new CyclicA(); 52 | b.value = 2; 53 | CyclicA c = new CyclicA(); 54 | c.value = 3; 55 | CyclicResult result = new CyclicResult(); 56 | result.a = a; 57 | result.b = b; 58 | result.a.ref = c; 59 | result.b.ref = c; 60 | return result; 61 | } 62 | }; 63 | testCycle(impl, 1, 2, 3, 3); 64 | } 65 | 66 | @Test //Issue #6: Cyclic Reference if Custom Type is used more than once 67 | public void testIssue006_withCycle() throws Exception { 68 | CyclicReferenceBugImpl impl = new CyclicReferenceBugImpl() { 69 | public CyclicResult getResult() { 70 | CyclicA a = new CyclicA(); 71 | a.value = 1; 72 | CyclicA b = new CyclicA(); 73 | b.value = 2; 74 | CyclicResult result = new CyclicResult(); 75 | result.a = a; 76 | result.b = b; 77 | result.a.ref = b; 78 | result.b.ref = a; 79 | return result; 80 | } 81 | }; 82 | try { 83 | testCycle(impl, 1, 2, 2, 1); 84 | fail("should have thrown " + JsonRpcRemoteException.class.getName() + " with cause = " + StackOverflowError.class.getName()); 85 | } catch (JsonRpcRemoteException e) { 86 | assertTrue(e.getMsg().indexOf("Caused by java.lang.StackOverflowError") > 0); 87 | // Be lenient and let the developer, handle this error 88 | } 89 | } 90 | 91 | private void testCycle(CyclicReferenceBugImpl impl, int a, int b, Integer refA, Integer refB) { 92 | executor.addHandler("impl", impl, CyclicReferenceBug.class); 93 | 94 | JsonRpcInvoker invoker = new JsonRpcInvoker(); 95 | CyclicReferenceBug bug = invoker.get(new JsonRpcClientTransport() { 96 | public String call(String requestData) throws Exception { 97 | final String request = requestData; 98 | final StringBuilder response = new StringBuilder(); 99 | executor.execute(new JsonRpcServerTransport() { 100 | public String readRequest() throws Exception { 101 | return request; 102 | } 103 | 104 | public void writeResponse(String responseData) throws Exception { 105 | response.append(responseData); 106 | } 107 | }); 108 | return response.toString(); 109 | } 110 | }, "impl", CyclicReferenceBug.class); 111 | 112 | CyclicResult result = bug.getResult(); 113 | assertNotNull(result); 114 | assertNotNull(result.a); 115 | assertNotNull(result.b); 116 | assertEquals(result.a.value, a); 117 | assertEquals(result.b.value, b); 118 | if (refA == null) { 119 | assertNull(result.a.ref); 120 | } else { 121 | assertEquals(result.a.ref.value, refA.intValue()); 122 | } 123 | if (refB == null) { 124 | assertNull(result.b.ref); 125 | } else { 126 | assertEquals(result.b.ref.value, refB.intValue()); 127 | } 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /jsonrpc-java/src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.logger.org.json.rpc = DEBUG 2 | 3 | # Root logger option 4 | log4j.rootLogger = INFO, stream 5 | 6 | # Direct log messages to stdout 7 | log4j.appender.stream = org.apache.log4j.ConsoleAppender 8 | log4j.appender.stream.Target = System.out 9 | log4j.appender.stream.layout = org.apache.log4j.PatternLayout 10 | log4j.appender.stream.layout.ConversionPattern=[%5p] %c{1}:%L - %m%n 11 | 12 | -------------------------------------------------------------------------------- /jsonrpc-js/jsonrpc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 ritwik.net 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | * This code has been originally taken from JSON/XML-RPC Client 17 | * . 18 | * 19 | * It has been modified to support only JSON-RPC 20 | * 21 | * JSON/XML-RPC Client 22 | * Version: 0.8.0.2 (2007-12-06) 23 | * Copyright: 2007, Weston Ruter 24 | * License: Dual licensed under MIT 25 | * and GPL licenses. 26 | * 27 | * Original inspiration for the design of this implementation is from jsolait, from which 28 | * are taken the "ServiceProxy" name and the interface for synchronous method calls. 29 | */ 30 | var JsonRpc = { 31 | version:"1.0.0.1", 32 | requestCount: 0 33 | }; 34 | JsonRpc.ServiceProxy = function (serviceUrl, options) { 35 | this.__serviceURL = serviceUrl; 36 | this.__isCrossSite = false; 37 | 38 | var urlParts = this.__serviceURL.match(/^(\w+:)\/\/([^\/]+?)(?::(\d+))?(?:$|\/)/); 39 | if (urlParts) { 40 | this.__isCrossSite = ( 41 | location.protocol != urlParts[1] || 42 | document.domain != urlParts[2] || 43 | location.port != (urlParts[3] || "") 44 | ); 45 | } 46 | 47 | if (this.__isCrossSite) { 48 | throw new Error("Cross site rpc not supported yet"); 49 | } 50 | 51 | //Set other default options 52 | var providedMethodList; 53 | this.__isAsynchronous = true; 54 | this.__authUsername = null; 55 | this.__authPassword = null; 56 | this.__dateEncoding = 'ISO8601'; // ("@timestamp@" || "@ticks@") || "classHinting" || "ASP.NET" 57 | this.__decodeISO8601 = true; //JSON only 58 | 59 | //Get the provided options 60 | if (options instanceof Object) { 61 | if (options.asynchronous !== undefined) { 62 | this.__isAsynchronous = !!options.asynchronous; 63 | } 64 | if (options.user != undefined) 65 | this.__authUsername = options.user; 66 | if (options.password != undefined) 67 | this.__authPassword = options.password; 68 | if (options.dateEncoding != undefined) 69 | this.__dateEncoding = options.dateEncoding; 70 | if (options.decodeISO8601 != undefined) 71 | this.__decodeISO8601 = !!options.decodeISO8601; 72 | providedMethodList = options.methods; 73 | } 74 | 75 | // Obtain the list of methods made available by the server 76 | if (providedMethodList) { 77 | this.__methodList = providedMethodList; 78 | } else { 79 | var async = this.__isAsynchronous; 80 | this.__isAsynchronous = false; 81 | this.__methodList = this.__callMethod("system.listMethods", []); 82 | this.__isAsynchronous = async; 83 | } 84 | this.__methodList.push("system.listMethods"); 85 | 86 | //Create local "wrapper" functions which reference the methods obtained above 87 | for (var methodName, i = 0; methodName = this.__methodList[i]; i++) { 88 | //Make available the received methods in the form of chained property lists (eg. "parent.child.methodName") 89 | var methodObject = this; 90 | var propChain = methodName.split(/\./); 91 | for (var j = 0; j + 1 < propChain.length; j++) { 92 | if (!methodObject[propChain[j]]) 93 | methodObject[propChain[j]] = {}; 94 | methodObject = methodObject[propChain[j]]; 95 | } 96 | 97 | //Create a wrapper to this.__callMethod with this instance and this methodName bound 98 | var wrapper = (function(instance, methodName) { 99 | var call = {instance:instance, methodName:methodName}; //Pass parameters into closure 100 | return function() { 101 | if (call.instance.__isAsynchronous) { 102 | if (arguments.length == 1 && arguments[0] instanceof Object) { 103 | call.instance.__callMethod(call.methodName, 104 | arguments[0].params, 105 | arguments[0].onSuccess, 106 | arguments[0].onException, 107 | arguments[0].onComplete); 108 | } 109 | else { 110 | call.instance.__callMethod(call.methodName, 111 | arguments[0], 112 | arguments[1], 113 | arguments[2], 114 | arguments[3]); 115 | } 116 | return undefined; 117 | } 118 | else return call.instance.__callMethod(call.methodName, JsonRpc.toArray(arguments)); 119 | }; 120 | })(this, methodName); 121 | methodObject[propChain[propChain.length - 1]] = wrapper; 122 | } 123 | }; 124 | 125 | JsonRpc.setAsynchronous = function(serviceProxy, isAsynchronous) { 126 | serviceProxy.__isAsynchronous = !!isAsynchronous; 127 | }; 128 | 129 | 130 | JsonRpc.ServiceProxy.prototype.__callMethod = function(methodName, params, successHandler, exceptionHandler, completeHandler) { 131 | JsonRpc.requestCount++; 132 | 133 | //Verify that successHandler, exceptionHandler, and completeHandler are functions 134 | if (this.__isAsynchronous) { 135 | if (successHandler && typeof successHandler != 'function') 136 | throw Error('The asynchronous onSuccess handler callback function you provided is invalid; the value you provided (' + successHandler.toString() + ') is of type "' + typeof(successHandler) + '".'); 137 | if (exceptionHandler && typeof exceptionHandler != 'function') 138 | throw Error('The asynchronous onException handler callback function you provided is invalid; the value you provided (' + exceptionHandler.toString() + ') is of type "' + typeof(exceptionHandler) + '".'); 139 | if (completeHandler && typeof completeHandler != 'function') 140 | throw Error('The asynchronous onComplete handler callback function you provided is invalid; the value you provided (' + completeHandler.toString() + ') is of type "' + typeof(completeHandler) + '".'); 141 | } 142 | 143 | try { 144 | //Assign the provided callback function to the response lookup table 145 | if (this.__isAsynchronous) { 146 | JsonRpc.pendingRequests[String(JsonRpc.requestCount)] = { 147 | //method:methodName, 148 | onSuccess:successHandler, 149 | onException:exceptionHandler, 150 | onComplete:completeHandler 151 | }; 152 | } 153 | 154 | //Obtain and verify the parameters 155 | if (params && (!(params instanceof Object) || params instanceof Date)) //JSON-RPC 1.1 allows params to be a hash not just an array 156 | throw Error('When making asynchronous calls, the parameters for the method must be passed as an array (or a hash); the value you supplied (' + String(params) + ') is of type "' + typeof(params) + '".'); 157 | 158 | //Prepare the XML-RPC request 159 | var request,postData; 160 | request = { 161 | version:"2.0", 162 | method:methodName, 163 | id:JsonRpc.requestCount 164 | }; 165 | if (params) 166 | request.params = params; 167 | postData = this.__toJSON(request); 168 | 169 | 170 | //XMLHttpRequest chosen (over Ajax.Request) because it propogates uncaught exceptions 171 | var xhr; 172 | if (window.XMLHttpRequest) 173 | xhr = new XMLHttpRequest(); 174 | else if (window.ActiveXObject) { 175 | try { 176 | xhr = new ActiveXObject('Msxml2.XMLHTTP'); 177 | } catch(err) { 178 | xhr = new ActiveXObject('Microsoft.XMLHTTP'); 179 | } 180 | } 181 | xhr.open('POST', this.__serviceURL, this.__isAsynchronous, this.__authUsername, this.__authPassword); 182 | xhr.setRequestHeader('Content-Type', 'application/json'); 183 | xhr.setRequestHeader('Accept', 'application/json'); 184 | 185 | if (this.__isAsynchronous) { 186 | //Send the request 187 | xhr.send(postData); 188 | 189 | //Handle the response 190 | var instance = this; 191 | var requestInfo = {id:JsonRpc.requestCount}; //for XML-RPC since the 'request' object cannot contain request ID 192 | xhr.onreadystatechange = function() { 193 | if (xhr.readyState == 4) { 194 | //XML-RPC 195 | var response = instance.__evalJSON(xhr.responseText, instance.__isResponseSanitized); 196 | if (!response.id) 197 | response.id = requestInfo.id; 198 | instance.__doCallback(response); 199 | } 200 | }; 201 | 202 | return undefined; 203 | } else { 204 | //Send the request 205 | xhr.send(postData); 206 | var response; 207 | response = this.__evalJSON(xhr.responseText, this.__isResponseSanitized); 208 | 209 | //Note that this error must be caught with a try/catch block instead of by passing a onException callback 210 | if (response.error) 211 | throw Error('Unable to call "' + methodName + '". Server responsed with error (code ' + response.error.code + '): ' + response.error.message); 212 | 213 | this.__upgradeValuesFromJSON(response); 214 | return response.result; 215 | } 216 | 217 | } catch(err) { 218 | //err.locationCode = PRE-REQUEST Cleint 219 | var isCaught = false; 220 | if (exceptionHandler) 221 | isCaught = exceptionHandler(err); //add error location 222 | if (completeHandler) 223 | completeHandler(); 224 | 225 | if (!isCaught) 226 | throw err; 227 | } 228 | }; 229 | 230 | 231 | //This acts as a lookup table for the response callback to execute the user-defined 232 | // callbacks and to clean up after a request 233 | JsonRpc.pendingRequests = {}; 234 | 235 | //Ad hoc cross-site callback functions keyed by request ID; when a cross-site request 236 | // is made, a function is created 237 | JsonRpc.callbacks = {}; 238 | 239 | //Called by asychronous calls when their responses have loaded 240 | JsonRpc.ServiceProxy.prototype.__doCallback = function(response) { 241 | if (typeof response != 'object') 242 | throw Error('The server did not respond with a response object.'); 243 | if (!response.id) 244 | throw Error('The server did not respond with the required response id for asynchronous calls.'); 245 | 246 | if (!JsonRpc.pendingRequests[response.id]) 247 | throw Error('Fatal error with RPC code: no ID "' + response.id + '" found in pendingRequests.'); 248 | 249 | //Remove the SCRIPT element from the DOM tree for cross-site (JSON-in-Script) requests 250 | if (JsonRpc.pendingRequests[response.id].scriptElement) { 251 | var script = JsonRpc.pendingRequests[response.id].scriptElement; 252 | script.parentNode.removeChild(script); 253 | } 254 | //Remove the ad hoc cross-site callback function 255 | if (JsonRpc.callbacks[response.id]) 256 | delete JsonRpc.callbacks['r' + response.id]; 257 | 258 | var uncaughtExceptions = []; 259 | 260 | //Handle errors returned by the server 261 | if (response.error !== undefined) { 262 | var err = new Error(response.error.message); 263 | err.code = response.error.code; 264 | //err.locationCode = SERVER 265 | if (JsonRpc.pendingRequests[response.id].onException) { 266 | try { 267 | if (!JsonRpc.pendingRequests[response.id].onException(err)) 268 | uncaughtExceptions.push(err); 269 | } 270 | catch(err2) { //If the onException handler also fails 271 | uncaughtExceptions.push(err); 272 | uncaughtExceptions.push(err2); 273 | } 274 | } 275 | else uncaughtExceptions.push(err); 276 | } 277 | 278 | //Process the valid result 279 | else if (response.result !== undefined) { 280 | //iterate over all values and substitute date strings with Date objects 281 | //Note that response.result is not passed because the values contained 282 | // need to be modified by reference, and the only way to do so is 283 | // but accessing an object's properties. Thus an extra level of 284 | // abstraction allows for accessing all of the results members by reference. 285 | this.__upgradeValuesFromJSON(response); 286 | 287 | if (JsonRpc.pendingRequests[response.id].onSuccess) { 288 | try { 289 | JsonRpc.pendingRequests[response.id].onSuccess(response.result); 290 | } 291 | //If the onSuccess callback itself fails, then call the onException handler as above 292 | catch(err) { 293 | //err3.locationCode = CLIENT; 294 | if (JsonRpc.pendingRequests[response.id].onException) { 295 | try { 296 | if (!JsonRpc.pendingRequests[response.id].onException(err)) 297 | uncaughtExceptions.push(err); 298 | } 299 | catch(err2) { //If the onException handler also fails 300 | uncaughtExceptions.push(err); 301 | uncaughtExceptions.push(err2); 302 | } 303 | } 304 | else uncaughtExceptions.push(err); 305 | } 306 | } 307 | } 308 | 309 | //Call the onComplete handler 310 | try { 311 | if (JsonRpc.pendingRequests[response.id].onComplete) 312 | JsonRpc.pendingRequests[response.id].onComplete(response); 313 | } 314 | catch(err) { //If the onComplete handler fails 315 | //err3.locationCode = CLIENT; 316 | if (JsonRpc.pendingRequests[response.id].onException) { 317 | try { 318 | if (!JsonRpc.pendingRequests[response.id].onException(err)) 319 | uncaughtExceptions.push(err); 320 | } 321 | catch(err2) { //If the onException handler also fails 322 | uncaughtExceptions.push(err); 323 | uncaughtExceptions.push(err2); 324 | } 325 | } 326 | else uncaughtExceptions.push(err); 327 | } 328 | 329 | delete JsonRpc.pendingRequests[response.id]; 330 | 331 | //Merge any exception raised by onComplete into the previous one(s) and throw it 332 | if (uncaughtExceptions.length) { 333 | var code; 334 | var message = 'There ' + (uncaughtExceptions.length == 1 ? 335 | 'was 1 uncaught exception' : 336 | 'were ' + uncaughtExceptions.length + ' uncaught exceptions') + ': '; 337 | for (var i = 0; i < uncaughtExceptions.length; i++) { 338 | if (i) 339 | message += "; "; 340 | message += uncaughtExceptions[i].message; 341 | if (uncaughtExceptions[i].code) 342 | code = uncaughtExceptions[i].code; 343 | } 344 | var err = new Error(message); 345 | err.code = code; 346 | throw err; 347 | } 348 | }; 349 | 350 | 351 | /******************************************************************************************* 352 | * JSON-RPC Specific Functions 353 | ******************************************************************************************/ 354 | JsonRpc.ServiceProxy.prototype.__toJSON = function(value) { 355 | switch (typeof value) { 356 | case 'number': 357 | return isFinite(value) ? value.toString() : 'null'; 358 | case 'boolean': 359 | return value.toString(); 360 | case 'string': 361 | //Taken from Ext JSON.js 362 | var specialChars = { 363 | "\b": '\\b', 364 | "\t": '\\t', 365 | "\n": '\\n', 366 | "\f": '\\f', 367 | "\r": '\\r', 368 | '"' : '\\"', 369 | "\\": '\\\\', 370 | "/" : '\/' 371 | }; 372 | return '"' + value.replace(/([\x00-\x1f\\"])/g, function(a, b) { 373 | var c = specialChars[b]; 374 | if (c) 375 | return c; 376 | c = b.charCodeAt(); 377 | //return "\\u00" + Math.floor(c / 16).toString(16) + (c % 16).toString(16); 378 | return '\\u00' + JsonRpc.zeroPad(c.toString(16)); 379 | }) + '"'; 380 | case 'object': 381 | if (value === null) 382 | return 'null'; 383 | else if (value instanceof Array) { 384 | var json = ['[']; //Ext's JSON.js reminds me that Array.join is faster than += in MSIE 385 | for (var i = 0; i < value.length; i++) { 386 | if (i) 387 | json.push(','); 388 | json.push(this.__toJSON(value[i])); 389 | } 390 | json.push(']'); 391 | return json.join(''); 392 | } 393 | else if (value instanceof Date) { 394 | switch (this.__dateEncoding) { 395 | case 'classHinting': //{"__jsonclass__":["constructor", [param1,...]], "prop1": ...} 396 | return '{"__jsonclass__":["Date",[' + value.valueOf() + ']]}'; 397 | case '@timestamp@': 398 | case '@ticks@': 399 | return '"@' + value.valueOf() + '@"'; 400 | case 'ASP.NET': 401 | return '"\\/Date(' + value.valueOf() + ')\\/"'; 402 | default: 403 | return '"' + JsonRpc.dateToISO8601(value) + '"'; 404 | } 405 | } 406 | else if (value instanceof Number || value instanceof String || value instanceof Boolean) 407 | return this.__toJSON(value.valueOf()); 408 | else { 409 | var useHasOwn = {}.hasOwnProperty ? true : false; //From Ext's JSON.js 410 | var json = ['{']; 411 | for (var key in value) { 412 | if (!useHasOwn || value.hasOwnProperty(key)) { 413 | if (json.length > 1) 414 | json.push(','); 415 | json.push(this.__toJSON(key) + ':' + this.__toJSON(value[key])); 416 | } 417 | } 418 | json.push('}'); 419 | return json.join(''); 420 | } 421 | //case 'undefined': 422 | //case 'function': 423 | //case 'unknown': 424 | //default: 425 | } 426 | throw new TypeError('Unable to convert the value of type "' + typeof(value) + '" to JSON.'); //(' + String(value) + ') 427 | }; 428 | 429 | JsonRpc.isJSON = function(string) { //from Prototype String.isJSON() 430 | var testStr = string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); 431 | return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(testStr); 432 | }; 433 | 434 | JsonRpc.ServiceProxy.prototype.__evalJSON = function(json, sanitize) { //from Prototype String.evalJSON() 435 | //Remove security comment delimiters 436 | json = json.replace(/^\/\*-secure-([\s\S]*)\*\/\s*$/, "$1"); 437 | var err; 438 | try { 439 | if (!sanitize || JsonRpc.isJSON(json)) 440 | return eval('(' + json + ')'); 441 | } 442 | catch(e) { 443 | err = e; 444 | } 445 | throw new SyntaxError('Badly formed JSON string: ' + json + " ... " + (err ? err.message : '')); 446 | }; 447 | 448 | //This function iterates over the properties of the passed object and converts them 449 | // into more appropriate data types, i.e. ISO8601 strings are converted to Date objects. 450 | JsonRpc.ServiceProxy.prototype.__upgradeValuesFromJSON = function(obj) { 451 | var matches, useHasOwn = {}.hasOwnProperty ? true : false; 452 | for (var key in obj) { 453 | if (!useHasOwn || obj.hasOwnProperty(key)) { 454 | //Parse date strings 455 | if (typeof obj[key] == 'string') { 456 | //ISO8601 457 | if (this.__decodeISO8601 && (matches = obj[key].match(/^(?:(\d\d\d\d)-(\d\d)(?:-(\d\d)(?:T(\d\d)(?::(\d\d)(?::(\d\d)(?:\.(\d+))?)?)?)?)?)$/))) { 458 | obj[key] = new Date(0); 459 | if (matches[1]) obj[key].setUTCFullYear(parseInt(matches[1])); 460 | if (matches[2]) obj[key].setUTCMonth(parseInt(matches[2] - 1)); 461 | if (matches[3]) obj[key].setUTCDate(parseInt(matches[3])); 462 | if (matches[4]) obj[key].setUTCHours(parseInt(matches[4])); 463 | if (matches[5]) obj[key].setUTCMinutes(parseInt(matches[5])); 464 | if (matches[6]) obj[key].setUTCMilliseconds(parseInt(matches[6])); 465 | } 466 | //@timestamp@ / @ticks@ 467 | else if (matches = obj[key].match(/^@(\d+)@$/)) { 468 | obj[key] = new Date(parseInt(matches[1])) 469 | } 470 | //ASP.NET 471 | else if (matches = obj[key].match(/^\/Date\((\d+)\)\/$/)) { 472 | obj[key] = new Date(parseInt(matches[1])) 473 | } 474 | } 475 | else if (obj[key] instanceof Object) { 476 | 477 | //JSON 1.0 Class Hinting: {"__jsonclass__":["constructor", [param1,...]], "prop1": ...} 478 | if (obj[key].__jsonclass__ instanceof Array) { 479 | //console.info('good1'); 480 | if (obj[key].__jsonclass__[0] == 'Date') { 481 | //console.info('good2'); 482 | if (obj[key].__jsonclass__[1] instanceof Array && obj[key].__jsonclass__[1][0]) 483 | obj[key] = new Date(obj[key].__jsonclass__[1][0]); 484 | else 485 | obj[key] = new Date(); 486 | } 487 | } 488 | else this.__upgradeValuesFromJSON(obj[key]); 489 | } 490 | } 491 | } 492 | }; 493 | 494 | 495 | /******************************************************************************************* 496 | * Other helper functions 497 | ******************************************************************************************/ 498 | 499 | //Takes an array or hash and coverts it into a query string, converting dates to ISO8601 500 | // and throwing an exception if nested hashes or nested arrays appear. 501 | JsonRpc.toQueryString = function(params) { 502 | if (!(params instanceof Object || params instanceof Array) || params instanceof Date) 503 | throw Error('You must supply either an array or object type to convert into a query string. You supplied: ' + params.constructor); 504 | 505 | var str = ''; 506 | var useHasOwn = {}.hasOwnProperty ? true : false; 507 | 508 | for (var key in params) { 509 | if (useHasOwn && params.hasOwnProperty(key)) { 510 | //Process an array 511 | if (params[key] instanceof Array) { 512 | for (var i = 0; i < params[key].length; i++) { 513 | if (str) 514 | str += '&'; 515 | str += encodeURIComponent(key) + "="; 516 | if (params[key][i] instanceof Date) 517 | str += encodeURIComponent(JsonRpc.dateToISO8601(params[key][i])); 518 | else if (params[key][i] instanceof Object) 519 | throw Error('Unable to pass nested arrays nor objects as parameters while in making a cross-site request. The object in question has this constructor: ' + params[key][i].constructor); 520 | else str += encodeURIComponent(String(params[key][i])); 521 | } 522 | } 523 | else { 524 | if (str) 525 | str += '&'; 526 | str += encodeURIComponent(key) + "="; 527 | if (params[key] instanceof Date) 528 | str += encodeURIComponent(JsonRpc.dateToISO8601(params[key])); 529 | else if (params[key] instanceof Object) 530 | throw Error('Unable to pass objects as parameters while in making a cross-site request. The object in question has this constructor: ' + params[key].constructor); 531 | else str += encodeURIComponent(String(params[key])); 532 | } 533 | } 534 | } 535 | return str; 536 | }; 537 | 538 | //Converts an iterateable value into an array; similar to Prototype's $A function 539 | JsonRpc.toArray = function(value) { 540 | //if(value && value.length){ 541 | if (value instanceof Array) 542 | return value; 543 | var array = []; 544 | for (var i = 0; i < value.length; i++) 545 | array.push(value[i]); 546 | return array; 547 | //} 548 | //throw Error("Unable to convert to an array the value: " + String(value)); 549 | }; 550 | 551 | //Returns an ISO8601 string *in UTC* for the provided date (Prototype's Date.toJSON() returns localtime) 552 | JsonRpc.dateToISO8601 = function(date) { 553 | //var jsonDate = date.toJSON(); 554 | //return jsonDate.substring(1, jsonDate.length-1); //strip double quotes 555 | 556 | return date.getUTCFullYear() + '-' + 557 | JsonRpc.zeroPad(date.getUTCMonth() + 1) + '-' + 558 | JsonRpc.zeroPad(date.getUTCDate()) + 'T' + 559 | JsonRpc.zeroPad(date.getUTCHours()) + ':' + 560 | JsonRpc.zeroPad(date.getUTCMinutes()) + ':' + 561 | JsonRpc.zeroPad(date.getUTCSeconds()) + '.' + 562 | //Prototype's Date.toJSON() method does not include milliseconds 563 | JsonRpc.zeroPad(date.getUTCMilliseconds(), 3); 564 | }; 565 | 566 | JsonRpc.zeroPad = function(value, width) { 567 | if (!width) 568 | width = 2; 569 | value = (value == undefined ? '' : String(value)) 570 | while (value.length < width) 571 | value = '0' + value; 572 | return value; 573 | }; 574 | --------------------------------------------------------------------------------