├── .github ├── dependabot.yml └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── client ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── arteam │ │ └── simplejsonrpc │ │ └── client │ │ ├── JsonRpcClient.java │ │ ├── JsonRpcId.java │ │ ├── JsonRpcParams.java │ │ ├── ParamsType.java │ │ ├── Transport.java │ │ ├── builder │ │ ├── AbstractBuilder.java │ │ ├── BatchRequestBuilder.java │ │ ├── NotificationRequestBuilder.java │ │ ├── ObjectApiBuilder.java │ │ ├── Reflections.java │ │ └── RequestBuilder.java │ │ ├── exception │ │ ├── JsonRpcBatchException.java │ │ └── JsonRpcException.java │ │ ├── generator │ │ ├── AtomicLongIdGenerator.java │ │ ├── CurrentTimeIdGenerator.java │ │ ├── IdGenerator.java │ │ ├── SecureRandomIdGenerator.java │ │ ├── SecureRandomIntIdGenerator.java │ │ ├── SecureRandomLongIdGenerator.java │ │ └── SecureRandomStringIdGenerator.java │ │ └── metadata │ │ ├── ClassMetadata.java │ │ ├── MethodMetadata.java │ │ └── ParameterMetadata.java │ └── test │ ├── java │ └── com │ │ └── github │ │ └── arteam │ │ └── simplejsonrpc │ │ └── client │ │ ├── BaseClientTest.java │ │ ├── BatchRequestBuilderTest.java │ │ ├── JsonRpcClientErrorsTest.java │ │ ├── JsonRpcClientNotificationsTest.java │ │ ├── JsonRpcClientTest.java │ │ ├── JsonRpcObjectAPITest.java │ │ ├── RequestResponse.java │ │ ├── builder │ │ └── BatchRequestBuilderErrorsTest.java │ │ ├── domain │ │ ├── Player.java │ │ ├── Position.java │ │ └── Team.java │ │ ├── generator │ │ ├── AtomicLongIdGeneratorTest.java │ │ ├── SecureRandomIntIdGeneratorTest.java │ │ ├── SecureRandomLongIdGeneratorTest.java │ │ └── SecureRandomStringIdGeneratorTest.java │ │ ├── object │ │ ├── BaseService.java │ │ ├── FixedIntegerIdGenerator.java │ │ ├── FixedStringIdGenerator.java │ │ ├── TeamService.java │ │ └── TestIdGenerator.java │ │ └── util │ │ └── MapBuilder.java │ └── resources │ ├── batch_requests.json │ └── client_test_data.json ├── core ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── arteam │ └── simplejsonrpc │ └── core │ ├── annotation │ ├── JsonRpcError.java │ ├── JsonRpcErrorData.java │ ├── JsonRpcMethod.java │ ├── JsonRpcOptional.java │ ├── JsonRpcParam.java │ └── JsonRpcService.java │ └── domain │ ├── ErrorMessage.java │ ├── ErrorResponse.java │ ├── Request.java │ ├── Response.java │ └── SuccessResponse.java ├── pom.xml └── server ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── arteam │ └── simplejsonrpc │ └── server │ ├── JsonRpcServer.java │ ├── Reflections.java │ └── metadata │ ├── ClassMetadata.java │ ├── ErrorDataResolver.java │ ├── MethodMetadata.java │ └── ParameterMetadata.java └── test ├── java └── com │ └── github │ └── arteam │ └── simplejsonrpc │ └── server │ ├── simple │ ├── JsonRpcErrorsTest.java │ ├── JsonRpcServiceTest.java │ ├── domain │ │ ├── Player.java │ │ ├── Position.java │ │ └── Team.java │ ├── exception │ │ ├── EmptyMessageTeamServiceException.java │ │ ├── ExceptionWithDataField.java │ │ ├── ExceptionWithDataGetter.java │ │ ├── ExceptionWithDataMultipleFields.java │ │ ├── ExceptionWithDataMultipleGetters.java │ │ ├── ExceptionWithDataMultipleMixed.java │ │ ├── ExceptionWithWrongMethods.java │ │ └── TeamServiceAuthException.java │ ├── service │ │ ├── BaseService.java │ │ ├── BogusService.java │ │ └── TeamService.java │ └── util │ │ └── RequestResponse.java │ └── spec │ ├── CalculatorService.java │ └── SpecTest.java └── resources ├── error ├── request │ ├── bad_array_generic_type.json │ ├── bad_generic_type.json │ ├── bad_id.json │ ├── bad_json.json │ ├── bad_map_generic_type.json │ ├── bad_method_type.json │ ├── bad_params_type.json │ ├── bad_version.json │ ├── batch_error.json │ ├── bogus_service.json │ ├── mandatory_parameter_explicitly_null.json │ ├── mandatory_parameter_is_not_set.json │ ├── method_with_double_params.json │ ├── no_method.json │ ├── no_version.json │ ├── not_annotated_method.json │ ├── not_existed_method.json │ ├── not_implemented_method.json │ ├── not_implemented_method_notification.json │ ├── not_json_rpc.json │ ├── not_json_rpc_20.json │ ├── not_json_service.json │ ├── not_public_method.json │ ├── param_annotation_is_not_specified.json │ ├── static_method.json │ ├── unspecified_parameter.json │ ├── user_exception.json │ ├── user_specified_error_data_field.json │ ├── user_specified_error_data_getter.json │ ├── user_specified_error_data_multiple_fields.json │ ├── user_specified_error_data_multiple_getters.json │ ├── user_specified_error_data_multiple_mixed.json │ ├── user_specified_error_data_wrong_methods.json │ ├── user_specified_error_message.json │ ├── wrong_amount_of_arguments_array.json │ ├── wrong_amount_of_arguments_map.json │ ├── wrong_parameter_name.json │ └── wrong_parameter_type.json └── response │ ├── batch_response.json │ ├── internal_error.json │ ├── invalid_params.json │ ├── invalid_request.json │ ├── invalid_request_with_id.json │ ├── method_not_found.json │ ├── parse_error.json │ ├── user_auth_error.json │ ├── user_specified_error_data_field.json │ ├── user_specified_error_data_getter.json │ ├── user_specified_error_data_wrong_methods.json │ └── user_specified_error_message.json ├── spec ├── test_1.properties ├── test_10.properties ├── test_11.properties ├── test_12.properties ├── test_13.properties ├── test_14.properties ├── test_15.properties ├── test_2.properties ├── test_3.properties ├── test_4.properties ├── test_5.properties ├── test_6.properties ├── test_7.properties ├── test_8.properties └── test_9.properties └── test_data.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | java: [ 17, 21 ] 18 | name: Java ${{ matrix.java }} 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup java 22 | uses: actions/setup-java@v3 23 | with: 24 | java-version: ${{ matrix.java }} 25 | distribution: temurin 26 | - name: Build with Maven 27 | run: mvn -B package --file pom.xml 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | target 3 | *.iml 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Artem Prigoda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Simple JSON-RPC 2 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.arteam/simple-json-rpc-client/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.arteam/simple-json-rpc-client/) 3 | =================== 4 | 5 | Library for a simple integration of the [JSON-RPC 2.0](http://www.jsonrpc.org/specification) protocol to a Java 6 | application. 7 | 8 | The goal is to provide a simple, fast and reliable way to integrate JSON-RPC 2.0 into a Java application on the server 9 | and/or the client side. You need to configure respectively `JsonRpcServer` or `JsonRpcClient` and implement transport code: 10 | the library takes care of the rest. No manual JSON transformation, reflection and manual error handling, just a service 11 | interface with annotations. Even this is not a requirement! There is a fluent API on the client side if you prefer 12 | builder style APIs. The library is a JSON-RPC 2.0 compliant implementation, so it should support handle all kind of 13 | JSON-RPC requests (correct or malformed). It doesn't depend on any transport protocol, an application server, or a DI 14 | framework. 15 | 16 | The library has a few dependencies: 17 | 18 | * **Jackson**, which is great for JSON parsing and data-binding; 19 | * **Guava**, which is great for caching and optional values (needed only for the server side) 20 | * **SL4J**, which is the standard for logging (needed only for server) 21 | * **IntelliJ Annotations**, which is great for providing compiler-time null checks. 22 | 23 | Submodules 24 | ----------- 25 | 26 | * [Client](https://github.com/arteam/simple-json-rpc/tree/master/client) 27 | * [Server](https://github.com/arteam/simple-json-rpc/tree/master/server) 28 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ## JSON-RPC 2.0 client 2 | 3 | `simple-json-rpc` client is a convenient way to access a JSON-RPC 2.0 server. It exposes two styles of API: 4 | 5 | * The fluent type-safe builder style API for generating requests and handling responses; 6 | * The object style API for representing a remote service as a Java interface. 7 | 8 | ### Client 9 | 10 | The basic class in API is `JsonRpcClient`. It's a factory for creating request builders: you configure it with a 11 | `Transport`. `JsonRpcClient` sends requests via the network and converts a response to a text/byte representation. 12 | You can specify a custom `ObjectMapper` which could be used for customization of JSON 13 | serialization and data binding. After that `JsonRpcClient` is ready for creating builders and proxies. 14 | 15 | #### Configuration 16 | 17 | ```java 18 | JsonRpcClient client = new JsonRpcClient(new Transport() { 19 | 20 | // Apache HttpClient is used as an example 21 | CloseableHttpClient httpClient = HttpClients.createDefault(); 22 | 23 | @Override 24 | public String pass(String request) throws IOException { 25 | HttpPost post = new HttpPost("http://json-rpc-server/team"); 26 | post.setEntity(new StringEntity(request, StandardCharsets.UTF_8)); 27 | post.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()); 28 | try (CloseableHttpResponse httpResponse = httpClient.execute(post)) { 29 | return EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); 30 | } 31 | } 32 | }); 33 | ``` 34 | 35 | ### Builder API 36 | 37 | The idea of this API is to provide a simple way to build a JSON-RPC request and handle a response without any JSON 38 | code. Builder is a great pattern for a such task. Builders are created via the `createRequest` method of 39 | a `JsonRpcClient`. They are focused on the requests data and require minimal boilerplate code. Builders 40 | are immutable and type-safe, so the actual request gets built only in the execution phase. 41 | 42 | #### Basic JSON-RPC request 43 | 44 | ```java 45 | Player player = client.createRequest() 46 | .method("findByInitials") 47 | .id(43121) 48 | .param("firstName","Steven") 49 | .param("lastName","Stamkos") 50 | .returnAs(Player.class) 51 | .execute(); 52 | ``` 53 | 54 | See more examples for using 55 | API [here](https://github.com/arteam/simple-json-rpc/blob/master/client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcClientTest.java) 56 | 57 | #### Notification JSON-RPC request 58 | 59 | ```java 60 | client.createNotification() 61 | .method("update") 62 | .param("cacheName", "profiles") 63 | .execute(); 64 | ``` 65 | 66 | More examples for using the 67 | API [here](https://github.com/arteam/simple-json-rpc/blob/master/client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcClientNotifications.java) 68 | 69 | #### Batch JSON-RPC request 70 | 71 | ```java 72 | Map result = client.createBatchRequest() 73 | .add("43121","findByInitials","Steven","Stamkos") 74 | .add("43122","findByInitials","Jack","Allen") 75 | .keysType(String.class) 76 | .returnType(Player.class) 77 | .execute(); 78 | ``` 79 | 80 | More examples for using the 81 | API [here](https://github.com/arteam/simple-json-rpc/blob/master/client/src/test/java/com/github/arteam/simplejsonrpc/client/BatchRequestBuilderTest.java) 82 | 83 | ## Object style API 84 | 85 | The idea of this API is to define an interface which models a remote service. The library generates a proxy on this 86 | interface which transforms method calls to JSON-RPC requests and JSON-RPC responses to returned values. The proxy is 87 | generated by calling the `onDemand` method of `JsonRpcClient`. After that your business code can safely work with the 88 | interface as a usual Java class without any JSON-RPC awareness. To enable this, you need to annotate your interface with 89 | the `@JsonRpcService`, `@JsonRpcMethod` and `@JsonRpcParam` annotations. 90 | 91 | * `@JsonRpcService` marks an interface as a JSON-RPC service. 92 | * `@JsonRpcMethod` marks a method as a JSON-RPC method. 93 | * `@JsonRpcParam` is a mandatory annotation for the method parameter, and it should contain a parameter name 94 | (this is mandatory because the Java compiler doesn't retain information about parameter names in a class file and 95 | therefore this information is not available in runtime). 96 | 97 | Additional annotations: 98 | 99 | * `@JsonRpcId` is used for defining the generator of request identifiers. `CurrentTimeIdGenerator` is used by default. 100 | * `@JsonRpcParams` is used for defining the type of JSON-RPC request params (an array or a map). It could be applied on 101 | an interface and on a method as well. 102 | * `@JsonRpcOptional` is used for marking method parameter as an optional. Parameter that marked with this annotation can 103 | accept null values. By default, all parameters are mandatory. 104 | 105 | ### Example 106 | 107 | **Service Interface** 108 | 109 | ```java 110 | @JsonRpcService 111 | @JsonRpcId(TestIdGenerator.class) 112 | @JsonRpcParams(ParamsType.MAP) 113 | public interface TeamService { 114 | 115 | @JsonRpcMethod 116 | boolean add(@JsonRpcParam("player") Player s); 117 | 118 | @JsonRpcMethod("find_by_birth_year") 119 | List findByBirthYear(@JsonRpcParam("birth_year") int birthYear); 120 | 121 | @JsonRpcMethod 122 | @JsonRpcParams(ParamsType.ARRAY) 123 | Player findByInitials(@JsonRpcParam("firstName") String firstName, 124 | @JsonRpcParam("lastName") String lastName); 125 | 126 | @JsonRpcMethod("findByInitials") 127 | Optional optionalFindByInitials(@JsonRpcParam("firstName") String firstName, 128 | @JsonRpcParam("lastName") String lastName); 129 | 130 | @JsonRpcMethod 131 | List find(@JsonRpcOptional @JsonRpcParam("position") @Nullable Position position, 132 | @JsonRpcOptional @JsonRpcParam("number") int number, 133 | @JsonRpcOptional @JsonRpcParam("team") Optional team, 134 | @JsonRpcOptional @JsonRpcParam("firstName") @Nullable String firstName, 135 | @JsonRpcOptional @JsonRpcParam("lastName") @Nullable String lastName, 136 | @JsonRpcOptional @JsonRpcParam("birthDate") @Nullable Date birthDate, 137 | @JsonRpcOptional @JsonRpcParam("capHit") Optional capHit); 138 | 139 | @JsonRpcMethod 140 | List findPlayersByFirstNames(@JsonRpcParam("names") List names); 141 | } 142 | ``` 143 | 144 | **Basic request** 145 | 146 | ```java 147 | Player player = client.onDemand(TeamService.class).findByInitials("Steven","Stamkos"); 148 | ``` 149 | 150 | More examples for using the 151 | API [here](https://github.com/arteam/simple-json-rpc/blob/master/client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcObjectAPITest.java) 152 | 153 | ## Setup 154 | 155 | Maven: 156 | 157 | ```xml 158 | 159 | com.github.arteam 160 | simple-json-rpc-client 161 | 1.3 162 | 163 | ``` 164 | 165 | ## Requirements 166 | 167 | JDK 11 and higher 168 | 169 | ## Dependencies 170 | 171 | * [Jackson](https://github.com/FasterXML/jackson) 172 | * [IntelliJ IDEA Annotations](http://mvnrepository.com/artifact/com.intellij/annotations/12.0) 173 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple-json-rpc 5 | com.github.arteam 6 | 1.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | simple-json-rpc-client 11 | Client implementation of JSON-RPC 2.0 12 | simple-json-rpc-client 13 | 14 | 15 | 16 | 17 | ${project.basedir}/src/test/resources 18 | 19 | 20 | 21 | 22 | 23 | 24 | com.github.arteam 25 | simple-json-rpc-core 26 | ${project.version} 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/JsonRpcClient.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.arteam.simplejsonrpc.client.builder.BatchRequestBuilder; 5 | import com.github.arteam.simplejsonrpc.client.builder.NotificationRequestBuilder; 6 | import com.github.arteam.simplejsonrpc.client.builder.ObjectApiBuilder; 7 | import com.github.arteam.simplejsonrpc.client.builder.RequestBuilder; 8 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 9 | 10 | import java.lang.reflect.Proxy; 11 | 12 | /** 13 | * Date: 8/9/14 14 | * Time: 8:58 PM 15 | *

16 | * JSON-RPC client. Represents a factory for a fluent client API {@link RequestBuilder}. 17 | * It's parametrized by {@link Transport} and Jackson {@link ObjectMapper} 18 | */ 19 | public class JsonRpcClient { 20 | 21 | /** 22 | * Transport for performing JSON-RPC requests and returning responses 23 | */ 24 | private final Transport transport; 25 | 26 | /** 27 | * JSON mapper for conversion between JSON and Java types 28 | */ 29 | private final ObjectMapper mapper; 30 | 31 | /** 32 | * Constructs a new JSON-RPC client with a specified transport 33 | * 34 | * @param transport transport implementation 35 | */ 36 | public JsonRpcClient(Transport transport) { 37 | this(transport, new ObjectMapper()); 38 | } 39 | 40 | /** 41 | * Constructs a new JSON-RPC client with a specified transport and user-definder JSON mapper 42 | * 43 | * @param transport transport implementation 44 | * @param mapper JSON mapper 45 | */ 46 | public JsonRpcClient(Transport transport, ObjectMapper mapper) { 47 | this.transport = transport; 48 | this.mapper = mapper; 49 | } 50 | 51 | /** 52 | * Creates a builder of a JSON-RPC request in initial state 53 | * 54 | * @return request builder 55 | */ 56 | public RequestBuilder createRequest() { 57 | return new RequestBuilder<>(transport, mapper); 58 | } 59 | 60 | /** 61 | * Creates a builder of a JSON-RPC notification request in initial state 62 | * 63 | * @return notification request builder 64 | */ 65 | public NotificationRequestBuilder createNotification() { 66 | return new NotificationRequestBuilder(transport, mapper); 67 | } 68 | 69 | /** 70 | * Creates a builder of a JSON-RPC batch request in initial state 71 | * 72 | * @return batch request builder 73 | */ 74 | public BatchRequestBuilder createBatchRequest() { 75 | return new BatchRequestBuilder<>(transport, mapper); 76 | } 77 | 78 | /** 79 | * Creates a new proxy for accessing a remote JSON-RPC service through an interface 80 | * 81 | * @param clazz interface metadata 82 | * @param interface type 83 | * @return a new proxy 84 | */ 85 | @SuppressWarnings("unchecked") 86 | public T onDemand(Class clazz) { 87 | return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, 88 | new ObjectApiBuilder(clazz, transport, mapper, null, null)); 89 | } 90 | 91 | /** 92 | * Creates a new proxy for accessing a remote JSON-RPC service through an interface 93 | * with a custom id generator that overrides the interface generator. 94 | * 95 | * @param clazz interface metadata 96 | * @param idGenerator custom id generator 97 | * @param interface type 98 | * @return a new proxy 99 | */ 100 | @SuppressWarnings("unchecked") 101 | public T onDemand(Class clazz, IdGenerator idGenerator) { 102 | return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, 103 | new ObjectApiBuilder(clazz, transport, mapper, null, idGenerator)); 104 | } 105 | 106 | /** 107 | * Creates a new proxy for accessing a remote JSON-RPC service through an interface 108 | * with a custom type of request params. 109 | * It applies for all methods and overrides interface and method level settings. 110 | * 111 | * @param clazz interface metadata 112 | * @param paramsType custom type of request params 113 | * @param interface type 114 | * @return a new proxy 115 | */ 116 | @SuppressWarnings("unchecked") 117 | public T onDemand(Class clazz, ParamsType paramsType) { 118 | return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, 119 | new ObjectApiBuilder(clazz, transport, mapper, paramsType, null)); 120 | } 121 | 122 | /** 123 | * Creates a new proxy for accessing a remote JSON-RPC service through an interface 124 | * with a custom id generator and custom type of request params. 125 | * The generator overrides the interface generator. 126 | * The type applies for all methods and overrides interface and method level settings. 127 | * 128 | * @param clazz interface metadata 129 | * @param idGenerator custom id generator 130 | * @param paramsType custom type of request params 131 | * @param interface type 132 | * @return a new proxy 133 | */ 134 | @SuppressWarnings("unchecked") 135 | public T onDemand(Class clazz, ParamsType paramsType, IdGenerator idGenerator) { 136 | return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, 137 | new ObjectApiBuilder(clazz, transport, mapper, paramsType, idGenerator)); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/JsonRpcId.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Date: 24.08.14 12 | * Time: 18:14 13 | */ 14 | @Target(ElementType.TYPE) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface JsonRpcId { 17 | 18 | Class> value(); 19 | } 20 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/JsonRpcParams.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Date: 11/4/14 11 | * Time: 10:45 PM 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.TYPE, ElementType.METHOD}) 15 | @Documented 16 | public @interface JsonRpcParams { 17 | 18 | ParamsType value() default ParamsType.MAP; 19 | } 20 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/ParamsType.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | /** 4 | * Date: 11/4/14 5 | * Time: 10:14 PM 6 | *

7 | * Style of JSON-RPC parameters representation (map or array) 8 | */ 9 | public enum ParamsType { 10 | MAP, ARRAY 11 | } 12 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/Transport.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Date: 8/9/14 7 | * Time: 8:52 PM 8 | *

Abstract transport for JSON-RPC communication

9 | */ 10 | public interface Transport { 11 | 12 | /** 13 | * Passes a JSON-RPC request in a text form to a backend and 14 | * returns a JSON-RPC response in a text form as well 15 | * 16 | * @param request JSON-RPC request as a string 17 | * @return JSON-RPC response as a string 18 | * @throws IOException if an I/O error happens during transfer 19 | */ 20 | String pass(String request) throws IOException; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/builder/AbstractBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.builder; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.node.ArrayNode; 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | import com.fasterxml.jackson.databind.node.ValueNode; 8 | import com.github.arteam.simplejsonrpc.client.Transport; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * Date: 10/12/14 14 | * Time: 6:48 PM 15 | *

16 | * Abstract builder for JSON-RPC requests 17 | */ 18 | public class AbstractBuilder { 19 | 20 | // Protocol constants 21 | protected static final String VERSION_2_0 = "2.0"; 22 | protected static final String RESULT = "result"; 23 | protected static final String ERROR = "error"; 24 | protected static final String JSONRPC = "jsonrpc"; 25 | protected static final String ID = "id"; 26 | protected static final String METHOD = "method"; 27 | protected static final String PARAMS = "params"; 28 | 29 | /** 30 | * Transport for performing a text request and returning a text response 31 | */ 32 | protected final Transport transport; 33 | 34 | /** 35 | * Jackson mapper for JSON processing 36 | */ 37 | protected final ObjectMapper mapper; 38 | 39 | public AbstractBuilder(Transport transport, ObjectMapper mapper) { 40 | this.transport = transport; 41 | this.mapper = mapper; 42 | } 43 | 44 | /** 45 | * Builds request params as a JSON array 46 | * 47 | * @param values request params 48 | * @return a new JSON array 49 | */ 50 | protected ArrayNode arrayParams(Object[] values) { 51 | ArrayNode newArrayParams = mapper.createArrayNode(); 52 | for (Object value : values) { 53 | newArrayParams.add(mapper.valueToTree(value)); 54 | } 55 | return newArrayParams; 56 | } 57 | 58 | /** 59 | * Builds request params as a JSON object 60 | * 61 | * @param params request params 62 | * @return a new JSON object 63 | */ 64 | protected ObjectNode objectParams(Map params) { 65 | ObjectNode objectNode = mapper.createObjectNode(); 66 | for (String key : params.keySet()) { 67 | objectNode.set(key, mapper.valueToTree(params.get(key))); 68 | } 69 | return objectNode; 70 | } 71 | 72 | /** 73 | * Creates a new JSON-RPC request as a JSON object 74 | * 75 | * @param id request id 76 | * @param method request method 77 | * @param params request params 78 | * @return a new request as a JSON object 79 | */ 80 | protected ObjectNode request(ValueNode id, String method, 81 | JsonNode params) { 82 | if (method.isEmpty()) { 83 | throw new IllegalArgumentException("Method is not set"); 84 | } 85 | ObjectNode requestNode = mapper.createObjectNode(); 86 | requestNode.put(JSONRPC, VERSION_2_0); 87 | requestNode.put(METHOD, method); 88 | requestNode.set(PARAMS, params); 89 | if (!id.isNull()) { 90 | requestNode.set(ID, id); 91 | } 92 | return requestNode; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/builder/NotificationRequestBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.builder; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.arteam.simplejsonrpc.client.Transport; 5 | 6 | /** 7 | * Date: 8/17/14 8 | * Time: 11:09 PM 9 | *

Type-safe builder of JSON-RPC notification requests.

10 | *

It uses underlying {@link RequestBuilder} to build a request, but not permits setting a request id. 11 | * Also it doesn't expect any response from the server, so there is no response parsing.

12 | */ 13 | public class NotificationRequestBuilder { 14 | 15 | /** 16 | * Delegated request builder 17 | */ 18 | private final RequestBuilder requestBuilder; 19 | 20 | /** 21 | * Creates a new notification request builder 22 | * 23 | * @param transport transport for request performing 24 | * @param mapper mapper for JSON processing 25 | */ 26 | public NotificationRequestBuilder(Transport transport, ObjectMapper mapper) { 27 | requestBuilder = new RequestBuilder<>(transport, mapper); 28 | } 29 | 30 | /** 31 | * Creates a new notification request builder as a chain of builders 32 | * 33 | * @param requestBuilder a new notification request builder 34 | */ 35 | private NotificationRequestBuilder(RequestBuilder requestBuilder) { 36 | this.requestBuilder = requestBuilder; 37 | } 38 | 39 | /** 40 | * Sets a request method 41 | * 42 | * @param method a request method 43 | * @return new builder 44 | */ 45 | public NotificationRequestBuilder method(String method) { 46 | return new NotificationRequestBuilder(requestBuilder.method(method)); 47 | } 48 | 49 | /** 50 | * Adds a new parameter to current request parameters. 51 | * 52 | * @param name parameter name 53 | * @param value parameter value 54 | * @return new builder 55 | */ 56 | public NotificationRequestBuilder param(String name, Object value) { 57 | return new NotificationRequestBuilder(requestBuilder.param(name, value)); 58 | } 59 | 60 | /** 61 | * Sets request parameters to request parameters. 62 | * Parameters are interpreted according to its positions. 63 | * 64 | * @param values array of parameters 65 | * @return new builder 66 | */ 67 | public NotificationRequestBuilder params(Object... values) { 68 | return new NotificationRequestBuilder(requestBuilder.params(values)); 69 | } 70 | 71 | /** 72 | * Execute a request through {@link Transport} 73 | */ 74 | public void execute() { 75 | requestBuilder.executeRequest(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/builder/ObjectApiBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.builder; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JavaType; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.node.ArrayNode; 8 | import com.fasterxml.jackson.databind.node.NullNode; 9 | import com.fasterxml.jackson.databind.node.ObjectNode; 10 | import com.fasterxml.jackson.databind.node.POJONode; 11 | import com.fasterxml.jackson.databind.node.ValueNode; 12 | import com.github.arteam.simplejsonrpc.client.ParamsType; 13 | import com.github.arteam.simplejsonrpc.client.Transport; 14 | import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException; 15 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 16 | import com.github.arteam.simplejsonrpc.client.metadata.ClassMetadata; 17 | import com.github.arteam.simplejsonrpc.client.metadata.MethodMetadata; 18 | import com.github.arteam.simplejsonrpc.client.metadata.ParameterMetadata; 19 | import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | import java.io.IOException; 23 | import java.lang.reflect.InvocationHandler; 24 | import java.lang.reflect.Method; 25 | 26 | /** 27 | * Date: 24.08.14 28 | * Time: 17:33 29 | *

30 | * Proxy for accessing a remote JSON-RPC service through an interface. 31 | */ 32 | public class ObjectApiBuilder extends AbstractBuilder implements InvocationHandler { 33 | 34 | @Nullable 35 | private final ParamsType userParamsType; 36 | 37 | @Nullable 38 | private final IdGenerator userIdGenerator; 39 | 40 | private final ClassMetadata classMetadata; 41 | 42 | /** 43 | * Crate a new proxy for an interface 44 | * 45 | * @param clazz service interface 46 | * @param transport transport abstraction 47 | * @param mapper json mapper 48 | * @param userParamsType custom type of request params 49 | * @param userIdGenerator custom id generator 50 | */ 51 | public ObjectApiBuilder(Class clazz, Transport transport, ObjectMapper mapper, 52 | @Nullable ParamsType userParamsType, @Nullable IdGenerator userIdGenerator) { 53 | super(transport, mapper); 54 | this.classMetadata = Reflections.getClassMetadata(clazz); 55 | this.userParamsType = userParamsType; 56 | this.userIdGenerator = userIdGenerator; 57 | } 58 | 59 | @Override 60 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 61 | // Check that it's a JSON-RPC method 62 | MethodMetadata methodMetadata = classMetadata.methods().get(method); 63 | if (methodMetadata == null) { 64 | throw new IllegalStateException("Method '" + method.getName() + "' is not JSON-RPC available"); 65 | } 66 | 67 | // Get method name (annotation or the actual name), params and id generator 68 | String methodName = methodMetadata.name(); 69 | JsonNode params = getParams(methodMetadata, args, getParamsType(classMetadata, methodMetadata)); 70 | IdGenerator idGenerator = userIdGenerator != null ? userIdGenerator : classMetadata.idGenerator(); 71 | 72 | // Construct a request 73 | ValueNode id = new POJONode(idGenerator.generate()); 74 | String textResponse = execute(request(id, methodName, params)); 75 | 76 | // Parse a response 77 | JsonNode responseNode = mapper.readTree(textResponse); 78 | JsonNode result = responseNode.get(RESULT); 79 | JsonNode error = responseNode.get(ERROR); 80 | if (result != null) { 81 | JavaType returnType = mapper.getTypeFactory().constructType(method.getGenericReturnType()); 82 | if (returnType.getRawClass() == void.class) { 83 | return null; 84 | } 85 | return mapper.convertValue(result, returnType); 86 | } else { 87 | ErrorMessage errorMessage = mapper.treeToValue(error, ErrorMessage.class); 88 | throw new JsonRpcException(errorMessage); 89 | } 90 | } 91 | 92 | /** 93 | * Get request params in a JSON representation (map or array) 94 | */ 95 | private JsonNode getParams(MethodMetadata method, @Nullable Object[] args, 96 | ParamsType paramsType) { 97 | ObjectNode paramsAsMap = mapper.createObjectNode(); 98 | ArrayNode paramsAsArray = mapper.createArrayNode(); 99 | for (String paramName : method.params().keySet()) { 100 | ParameterMetadata parameterMetadata = method.params().get(paramName); 101 | int index = parameterMetadata.getIndex(); 102 | JsonNode jsonArg = mapper.valueToTree(args != null ? args[index] : null); 103 | if (jsonArg == null || jsonArg == NullNode.instance) { 104 | if (parameterMetadata.isOptional()) { 105 | if (paramsType == ParamsType.ARRAY) { 106 | paramsAsArray.add(NullNode.instance); 107 | } 108 | } else { 109 | throw new IllegalArgumentException("Parameter '" + paramName + 110 | "' of method '" + method.name() + "' is mandatory and can't be null"); 111 | } 112 | } else { 113 | if (paramsType == ParamsType.MAP) { 114 | paramsAsMap.set(paramName, jsonArg); 115 | } else if (paramsType == ParamsType.ARRAY) { 116 | paramsAsArray.add(jsonArg); 117 | } 118 | } 119 | } 120 | return paramsType == ParamsType.MAP ? paramsAsMap : paramsAsArray; 121 | } 122 | 123 | /** 124 | * Execute a request on a remote service and return a textual representation of a response 125 | * 126 | * @param request json representation of a request 127 | * @return service response as a string 128 | */ 129 | private String execute(ObjectNode request) { 130 | try { 131 | return transport.pass(mapper.writeValueAsString(request)); 132 | } catch (JsonProcessingException e) { 133 | throw new IllegalArgumentException("Unable convert " + request + " to JSON", e); 134 | } catch (IOException e) { 135 | throw new IllegalStateException("I/O error during request processing", e); 136 | } 137 | } 138 | 139 | /** 140 | * Get style of params for a request. 141 | * It could be either on a method, class or user level. MAP is a fallback choice as default. 142 | * 143 | * @param classMetadata metadata of a service interface 144 | * @param methodMetadata metadata of a method 145 | * @return type of params 146 | */ 147 | private ParamsType getParamsType(ClassMetadata classMetadata, MethodMetadata methodMetadata) { 148 | if (userParamsType != null) { 149 | return userParamsType; 150 | } else if (methodMetadata.paramsType() != null) { 151 | return methodMetadata.paramsType(); 152 | } else if (classMetadata.paramsType() != null) { 153 | return classMetadata.paramsType(); 154 | } 155 | return ParamsType.MAP; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/builder/Reflections.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.builder; 2 | 3 | import com.github.arteam.simplejsonrpc.client.JsonRpcId; 4 | import com.github.arteam.simplejsonrpc.client.JsonRpcParams; 5 | import com.github.arteam.simplejsonrpc.client.ParamsType; 6 | import com.github.arteam.simplejsonrpc.client.generator.AtomicLongIdGenerator; 7 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 8 | import com.github.arteam.simplejsonrpc.client.metadata.ClassMetadata; 9 | import com.github.arteam.simplejsonrpc.client.metadata.MethodMetadata; 10 | import com.github.arteam.simplejsonrpc.client.metadata.ParameterMetadata; 11 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 12 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional; 13 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 14 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.lang.annotation.Annotation; 18 | import java.lang.reflect.Method; 19 | import java.util.HashMap; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * Date: 11/17/14 25 | * Time: 8:04 PM 26 | *

27 | * Utility class for gathering meta-information about client proxies through reflection 28 | */ 29 | class Reflections { 30 | 31 | private Reflections() { 32 | } 33 | 34 | /** 35 | * Gets remote service interface metadata 36 | * 37 | * @param clazz an interface for representing a remote service 38 | * @return class metadata 39 | */ 40 | public static ClassMetadata getClassMetadata(Class clazz) { 41 | Map methodsMetadata = new HashMap<>(32); 42 | Class searchClass = clazz; 43 | while (searchClass != null) { 44 | JsonRpcService rpcServiceAnn = getAnnotation(searchClass.getAnnotations(), JsonRpcService.class); 45 | if (rpcServiceAnn == null) { 46 | throw new IllegalStateException("Class '" + clazz.getCanonicalName() + 47 | "' is not annotated as @JsonRpcService"); 48 | } 49 | Method[] methods = searchClass.getMethods(); 50 | for (Method method : methods) { 51 | Annotation[] methodAnnotations = method.getDeclaredAnnotations(); 52 | JsonRpcMethod rpcMethodAnn = getAnnotation(methodAnnotations, JsonRpcMethod.class); 53 | if (rpcMethodAnn == null) { 54 | throw new IllegalStateException("Method '" + method.getName() + "' is not annotated as @JsonRpcMethod"); 55 | } 56 | 57 | // LinkedHashMap is needed to support method parameter ordering 58 | Map paramsMetadata = new LinkedHashMap<>(8); 59 | Annotation[][] parametersAnnotations = method.getParameterAnnotations(); 60 | for (int i = 0; i < parametersAnnotations.length; i++) { 61 | Annotation[] parametersAnnotation = parametersAnnotations[i]; 62 | // Check that it's a JSON-RPC param 63 | JsonRpcParam rpcParamAnn = getAnnotation(parametersAnnotation, JsonRpcParam.class); 64 | if (rpcParamAnn == null) { 65 | throw new IllegalStateException("Parameter with index=" + i + " of method '" + method.getName() + 66 | "' is not annotated with @JsonRpcParam"); 67 | } 68 | // Check that's a param could be an optional 69 | JsonRpcOptional optionalAnn = getAnnotation(parametersAnnotation, JsonRpcOptional.class); 70 | ParameterMetadata parameterMetadata = new ParameterMetadata(i, optionalAnn != null); 71 | if (paramsMetadata.put(rpcParamAnn.value(), parameterMetadata) != null) { 72 | throw new IllegalStateException("Two parameters of method '" + method.getName() + "' have the " + 73 | "same name '" + rpcParamAnn.value() + "'"); 74 | } 75 | 76 | } 77 | String name = !rpcMethodAnn.value().isEmpty() ? rpcMethodAnn.value() : method.getName(); 78 | ParamsType paramsType = getParamsType(methodAnnotations); 79 | methodsMetadata.put(method, new MethodMetadata(name, paramsType, paramsMetadata)); 80 | } 81 | searchClass = searchClass.getSuperclass(); 82 | } 83 | 84 | Annotation[] classAnnotations = clazz != null ? clazz.getDeclaredAnnotations() : null; 85 | IdGenerator idGenerator = getIdGenerator(classAnnotations); 86 | ParamsType paramsType = getParamsType(classAnnotations); 87 | return new ClassMetadata(paramsType, idGenerator, methodsMetadata); 88 | } 89 | 90 | 91 | /** 92 | * Get an actual id generator 93 | */ 94 | private static IdGenerator getIdGenerator(Annotation[] classAnnotations) { 95 | JsonRpcId jsonRpcIdAnn = getAnnotation(classAnnotations, JsonRpcId.class); 96 | Class> idGeneratorClazz = (jsonRpcIdAnn == null) ? 97 | AtomicLongIdGenerator.class : jsonRpcIdAnn.value(); 98 | try { 99 | return idGeneratorClazz.getDeclaredConstructor().newInstance(); 100 | } catch (Exception e) { 101 | throw new IllegalStateException("Unable instantiate id generator: " + idGeneratorClazz, e); 102 | } 103 | } 104 | 105 | @Nullable 106 | private static ParamsType getParamsType(Annotation[] annotations) { 107 | JsonRpcParams rpcParamsAnn = getAnnotation(annotations, JsonRpcParams.class); 108 | return rpcParamsAnn != null ? rpcParamsAnn.value() : null; 109 | 110 | } 111 | 112 | @SuppressWarnings("unchecked") 113 | @Nullable 114 | private static T getAnnotation(@Nullable Annotation[] annotations, Class clazz) { 115 | if (annotations != null) { 116 | for (Annotation annotation : annotations) { 117 | if (annotation != null && annotation.annotationType().equals(clazz)) { 118 | return (T) annotation; 119 | } 120 | } 121 | } 122 | return null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/exception/JsonRpcBatchException.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * Date: 10/13/14 9 | * Time: 8:17 PM 10 | *

11 | * Exception that occurs when batch JSON-RPC request is not completely successful 12 | */ 13 | public class JsonRpcBatchException extends RuntimeException { 14 | 15 | /** 16 | * Succeeded requests 17 | */ 18 | private final Map successes; 19 | 20 | /** 21 | * Failed requests 22 | */ 23 | private final Map errors; 24 | 25 | public JsonRpcBatchException(String message, Map successes, Map errors) { 26 | super(message); 27 | this.successes = successes; 28 | this.errors = errors; 29 | } 30 | 31 | 32 | public Map getSuccesses() { 33 | return successes; 34 | } 35 | 36 | 37 | public Map getErrors() { 38 | return errors; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/exception/JsonRpcException.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage; 4 | 5 | /** 6 | * Date: 8/9/14 7 | * Time: 10:08 PM 8 | *

9 | * Represents JSON-RPC error returned by a server 10 | */ 11 | public class JsonRpcException extends RuntimeException { 12 | 13 | /** 14 | * Actual error message 15 | */ 16 | private final ErrorMessage errorMessage; 17 | 18 | public JsonRpcException(ErrorMessage errorMessage) { 19 | super(errorMessage.toString()); 20 | this.errorMessage = errorMessage; 21 | } 22 | 23 | public ErrorMessage getErrorMessage() { 24 | return errorMessage; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/AtomicLongIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** 6 | * Date: 12/30/14 7 | * Time: 11:19 PM 8 | *

9 | * Return id from an atomic long counter. 10 | * It's the most reliable and straightforward way to generate identifiers 11 | */ 12 | public class AtomicLongIdGenerator implements IdGenerator { 13 | 14 | private final AtomicLong counter = new AtomicLong(0L); 15 | 16 | @Override 17 | public Long generate() { 18 | return counter.incrementAndGet(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/CurrentTimeIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | /** 4 | * Date: 24.08.14 5 | * Time: 18:20 6 | *

7 | * Return the current time as an id. 8 | * Not reliable if you need to guarantee uniqueness of request ids 9 | */ 10 | public class CurrentTimeIdGenerator implements IdGenerator { 11 | @Override 12 | public Long generate() { 13 | return System.currentTimeMillis(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/IdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | /** 4 | * Date: 24.08.14 5 | * Time: 18:12 6 | *

7 | * A strategy for the generation of request ids 8 | */ 9 | @FunctionalInterface 10 | public interface IdGenerator { 11 | 12 | T generate(); 13 | } 14 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import java.security.SecureRandom; 4 | 5 | /** 6 | * Date: 1/12/15 7 | * Time: 11:17 PM 8 | *

9 | * Abstract generator of secure random identifiers 10 | */ 11 | abstract class SecureRandomIdGenerator implements IdGenerator { 12 | 13 | protected final SecureRandom secureRandom; 14 | 15 | protected SecureRandomIdGenerator() { 16 | this(new SecureRandom()); 17 | } 18 | 19 | protected SecureRandomIdGenerator(SecureRandom secureRandom) { 20 | this.secureRandom = secureRandom; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIntIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | /** 4 | * Date: 1/12/15 5 | * Time: 11:38 PM 6 | *

7 | * Generates secure random positive integers under limit 8 | * By default the limit is 65536 9 | */ 10 | public class SecureRandomIntIdGenerator extends SecureRandomIdGenerator { 11 | 12 | private static final int DEFAULT_LIMIT = 65536; 13 | 14 | private final int limit; 15 | 16 | public SecureRandomIntIdGenerator() { 17 | limit = DEFAULT_LIMIT; 18 | } 19 | 20 | public SecureRandomIntIdGenerator(int limit) { 21 | if (limit <= 0) { 22 | throw new IllegalArgumentException("Limit should be positive"); 23 | } 24 | this.limit = limit; 25 | } 26 | 27 | @Override 28 | public Integer generate() { 29 | return secureRandom.nextInt(limit); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomLongIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | /** 4 | * Date: 1/12/15 5 | * Time: 11:12 PM 6 | *

7 | * Generate secure random positive long identifiers 8 | */ 9 | public class SecureRandomLongIdGenerator extends SecureRandomIdGenerator { 10 | 11 | @Override 12 | public Long generate() { 13 | return secureRandom.nextLong() >>> 1; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomStringIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | /** 4 | * Date: 12/30/14 5 | * Time: 11:45 PM 6 | *

7 | * Generate secure random strings consisting of HEX symbols with length of 40 characters 8 | */ 9 | public class SecureRandomStringIdGenerator extends SecureRandomIdGenerator { 10 | 11 | private static final char[] ALPHABET = "0123456789abcdef".toCharArray(); 12 | private static final int DEFAULT_CHUNK_SIZE = 20; 13 | 14 | private final int chunkSize; 15 | 16 | /** 17 | * Create a default generator 18 | */ 19 | public SecureRandomStringIdGenerator() { 20 | chunkSize = DEFAULT_CHUNK_SIZE; 21 | } 22 | 23 | /** 24 | * Create generator with a specific identifiers length 25 | * 26 | * @param idLength custom identifier length (it should be power of 2) 27 | */ 28 | public SecureRandomStringIdGenerator(int idLength) { 29 | if (idLength < 2) { 30 | throw new IllegalArgumentException("Bad message length: '" + idLength + "'. It should be >= 2"); 31 | } 32 | this.chunkSize = idLength / 2; 33 | } 34 | 35 | @Override 36 | public String generate() { 37 | byte[] buffer = new byte[chunkSize]; 38 | secureRandom.nextBytes(buffer); 39 | return hexString(buffer); 40 | } 41 | 42 | 43 | /** 44 | * Convert binary data to HEX representation. 45 | * Every byte is converted to 2 HEX symbols (one symbol for every 4 bits) 46 | * 47 | * @param source source chunk of data 48 | * @return string representation of the chunk as HEX values 49 | */ 50 | private static String hexString(byte[] source) { 51 | char[] result = new char[source.length * 2]; 52 | for (int i = 0; i < source.length; i++) { 53 | int unsigned = source[i] & 0xFF; 54 | int first4Bits = unsigned >>> 4; 55 | int last4Bits = unsigned & 0x0F; 56 | result[i * 2] = ALPHABET[first4Bits]; 57 | result[i * 2 + 1] = ALPHABET[last4Bits]; 58 | } 59 | return new String(result); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/metadata/ClassMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.metadata; 2 | 3 | import com.github.arteam.simplejsonrpc.client.ParamsType; 4 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.Map; 9 | 10 | /** 11 | * Date: 8/1/14 12 | * Time: 7:42 PM 13 | *

14 | * Metadata about a Java class 15 | */ 16 | public record ClassMetadata(@Nullable ParamsType paramsType, IdGenerator idGenerator, 17 | Map methods) { 18 | } 19 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/metadata/MethodMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.metadata; 2 | 3 | import com.github.arteam.simplejsonrpc.client.ParamsType; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Date: 8/1/14 10 | * Time: 7:42 PM 11 | *

12 | * Metadata about a Java method 13 | */ 14 | public record MethodMetadata(String name, 15 | @Nullable ParamsType paramsType, 16 | Map params) { 17 | } 18 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/arteam/simplejsonrpc/client/metadata/ParameterMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.metadata; 2 | 3 | /** 4 | * Date: 8/1/14 5 | * Time: 7:44 PM 6 | *

7 | * Method parameter metadata 8 | */ 9 | public record ParameterMetadata(int index, boolean optional) { 10 | 11 | public int getIndex() { 12 | return index; 13 | } 14 | 15 | public boolean isOptional() { 16 | return optional; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/BaseClientTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 8 | import org.junit.jupiter.api.BeforeAll; 9 | 10 | import java.util.Map; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | /** 15 | * Date: 24.08.14 16 | * Time: 18:25 17 | */ 18 | public class BaseClientTest { 19 | 20 | private static Map requestsResponses; 21 | 22 | private final ObjectMapper mapper = new ObjectMapper() 23 | .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) 24 | .registerModule(new Jdk8Module()); 25 | 26 | @BeforeAll 27 | public static void load() throws Exception { 28 | requestsResponses = new ObjectMapper() 29 | .readValue(BaseClientTest.class.getResource("/client_test_data.json"), new TypeReference<>() { 30 | }); 31 | } 32 | 33 | protected JsonRpcClient initClient(String testName) { 34 | final RequestResponse requestResponse = requestsResponses.get(testName); 35 | return new JsonRpcClient(request -> { 36 | System.out.println(request); 37 | JsonNode requestNode = mapper.readTree(request); 38 | assertThat(requestNode).isEqualTo(requestResponse.request); 39 | String response = mapper.writeValueAsString(requestResponse.response); 40 | System.out.println(response); 41 | return response; 42 | }, mapper); 43 | } 44 | 45 | protected JsonRpcClient fakeClient() { 46 | return new JsonRpcClient(request -> "", mapper); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/BatchRequestBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.github.arteam.simplejsonrpc.client.domain.Player; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | 17 | /** 18 | * Date: 10/12/14 19 | * Time: 9:38 PM 20 | */ 21 | public class BatchRequestBuilderTest { 22 | 23 | private static final TypeReference PLAYER_TYPE_REFERENCE = new TypeReference<>() { 24 | }; 25 | private static final ObjectMapper mapper = new ObjectMapper() 26 | .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 27 | 28 | private static Map requestsResponses; 29 | 30 | @BeforeAll 31 | public static void load() throws Exception { 32 | requestsResponses = mapper.readValue(BatchRequestBuilderTest.class.getResource("/batch_requests.json"), 33 | new TypeReference<>() { 34 | }); 35 | } 36 | 37 | private JsonRpcClient initClient(String testName) { 38 | final RequestResponse requestResponse = requestsResponses.get(testName); 39 | return new JsonRpcClient(request -> { 40 | System.out.println(request); 41 | JsonNode requestNode = mapper.readTree(request); 42 | assertThat(requestNode).isEqualTo(requestResponse.request); 43 | String response = mapper.writeValueAsString(requestResponse.response); 44 | System.out.println(response); 45 | return response; 46 | }, mapper); 47 | } 48 | 49 | private static Map stevenStamkos() { 50 | return Map.of("firstName", "Steven", "lastName", "Stamkos"); 51 | } 52 | 53 | private static Map jackAllen() { 54 | return Map.of("firstName", "Jack", "lastName", "Allen"); 55 | } 56 | 57 | private static Map vladimirSobotka() { 58 | return Map.of("firstName", "Vladimir", "lastName", "Sobotka"); 59 | } 60 | 61 | private static void checkBatch(Map result) { 62 | assertThat(result.get("43121").firstName()).isEqualTo("Steven"); 63 | assertThat(result.get("43121").lastName()).isEqualTo("Stamkos"); 64 | assertThat(result.get("43122").firstName()).isEqualTo("Jack"); 65 | assertThat(result.get("43122").lastName()).isEqualTo("Allen"); 66 | assertThat(result.get("43123")).isNull(); 67 | } 68 | 69 | @SuppressWarnings("unchecked") 70 | private static void checkUncheckedBatch(Map result) { 71 | assertThat(result.get("43121")).isExactlyInstanceOf(Player.class); 72 | assertThat(result.get("43122")).isExactlyInstanceOf(Player.class); 73 | checkBatch((Map) result); 74 | } 75 | 76 | @Test 77 | public void testBatchCommonType() { 78 | JsonRpcClient client = initClient("batch"); 79 | Map result = client.createBatchRequest() 80 | .add("43121", "findByInitials", stevenStamkos()) 81 | .add("43122", "findByInitials", jackAllen()) 82 | .add("43123", "findByInitials", vladimirSobotka()) 83 | .keysType(String.class) 84 | .returnType(Player.class) 85 | .execute(); 86 | checkBatch(result); 87 | } 88 | 89 | @Test 90 | public void testBatchDetailedTypes() { 91 | JsonRpcClient client = initClient("batch"); 92 | Map result = client.createBatchRequest() 93 | .add("43121", "findByInitials", stevenStamkos(), Player.class) 94 | .add("43122", "findByInitials", jackAllen(), Player.class) 95 | .add("43123", "findByInitials", vladimirSobotka(), Player.class) 96 | .keysType(String.class) 97 | .execute(); 98 | checkUncheckedBatch(result); 99 | } 100 | 101 | @Test 102 | public void testBatchTypeReferences() { 103 | JsonRpcClient client = initClient("batch"); 104 | Map result = client.createBatchRequest() 105 | .add("43121", "findByInitials", stevenStamkos(), PLAYER_TYPE_REFERENCE) 106 | .add("43122", "findByInitials", jackAllen(), PLAYER_TYPE_REFERENCE) 107 | .add("43123", "findByInitials", vladimirSobotka(), PLAYER_TYPE_REFERENCE) 108 | .keysType(String.class) 109 | .execute(); 110 | checkUncheckedBatch(result); 111 | } 112 | 113 | @Test 114 | public void testBatchArrayRequests() { 115 | JsonRpcClient client = initClient("batch_array"); 116 | Map result = client.createBatchRequest() 117 | .add("43121", "findByInitials", "Steven", "Stamkos") 118 | .add("43122", "findByInitials", "Jack", "Allen") 119 | .add("43123", "findByInitials", "Vladimir", "Sobotka") 120 | .keysType(String.class) 121 | .returnType(PLAYER_TYPE_REFERENCE) 122 | .execute(); 123 | checkBatch(result); 124 | } 125 | 126 | @Test 127 | public void testBatchArrayRequestsWithDetailedTypes() { 128 | JsonRpcClient client = initClient("batch_array"); 129 | Map result = client.createBatchRequest() 130 | .add("43121", "findByInitials", new Object[]{"Steven", "Stamkos"}, Player.class) 131 | .add("43122", "findByInitials", new Object[]{"Jack", "Allen"}, Player.class) 132 | .add("43123", "findByInitials", new Object[]{"Vladimir", "Sobotka"}, Player.class) 133 | .keysType(String.class) 134 | .execute(); 135 | checkUncheckedBatch(result); 136 | } 137 | 138 | @Test 139 | public void testBatchArrayRequestsWithTypeReferences() { 140 | JsonRpcClient client = initClient("batch_array"); 141 | Map result = client.createBatchRequest() 142 | .add("43121", "findByInitials", new Object[]{"Steven", "Stamkos"}, PLAYER_TYPE_REFERENCE) 143 | .add("43122", "findByInitials", new Object[]{"Jack", "Allen"}, PLAYER_TYPE_REFERENCE) 144 | .add("43123", "findByInitials", new Object[]{"Vladimir", "Sobotka"}, PLAYER_TYPE_REFERENCE) 145 | .keysType(String.class) 146 | .execute(); 147 | checkUncheckedBatch(result); 148 | } 149 | 150 | @Test 151 | @SuppressWarnings("unchecked") 152 | public void testDifferentRequests() { 153 | JsonRpcClient client = initClient("different_requests"); 154 | Map result = client.createBatchRequest() 155 | .add(12000, "isAlive", new HashMap<>(), Boolean.class) 156 | .add(12001, "findByInitials", new Object[]{"Kevin", "Shattenkirk"}, Player.class) 157 | .add(12002, "find_by_birth_year", Map.of("birth_year", 1990), 158 | new TypeReference>() { 159 | }) 160 | .keysType(Integer.class) 161 | .execute(); 162 | assertThat(result.get(12000)).isExactlyInstanceOf(Boolean.class); 163 | assertThat((Boolean) result.get(12000)).isTrue(); 164 | assertThat(result.get(12001)).isExactlyInstanceOf(Player.class); 165 | assertThat(((Player) result.get(12001)).firstName()).isEqualTo("Kevin"); 166 | assertThat(((Player) result.get(12001)).lastName()).isEqualTo("Shattenkirk"); 167 | assertThat(result.get(12002)).isInstanceOf(List.class); 168 | assertThat((List) result.get(12002)).hasSize(3); 169 | } 170 | 171 | @Test 172 | @SuppressWarnings("unchecked") 173 | public void testLongIds() { 174 | JsonRpcClient client = initClient("different_requests"); 175 | Map result = client.createBatchRequest() 176 | .add(12000L, "isAlive", new HashMap<>(), Boolean.class) 177 | .add(12001L, "findByInitials", new Object[]{"Kevin", "Shattenkirk"}, Player.class) 178 | .add(12002L, "find_by_birth_year", Map.of("birth_year", 1990), 179 | new TypeReference>() { 180 | }) 181 | .keysType(Long.class) 182 | .execute(); 183 | assertThat(result.get(12000L)).isExactlyInstanceOf(Boolean.class); 184 | assertThat((Boolean) result.get(12000L)).isTrue(); 185 | assertThat(result.get(12001L)).isExactlyInstanceOf(Player.class); 186 | assertThat(((Player) result.get(12001L)).firstName()).isEqualTo("Kevin"); 187 | assertThat(((Player) result.get(12001L)).lastName()).isEqualTo("Shattenkirk"); 188 | assertThat(result.get(12002L)).isInstanceOf(List.class); 189 | assertThat((List) result.get(12002L)).hasSize(3); 190 | } 191 | 192 | @Test 193 | public void testBatchWithNotifications() { 194 | JsonRpcClient client = initClient("batch_with_notification"); 195 | Map result = client.createBatchRequest() 196 | .add(1, "findByInitials", stevenStamkos(), Player.class) 197 | .add("updateCache") 198 | .add(2, "findByInitials", new Object[]{"Vladimir", "Sobotka"}, PLAYER_TYPE_REFERENCE) 199 | .keysType(Integer.class) 200 | .execute(); 201 | assertThat(result.get(1)).isExactlyInstanceOf(Player.class); 202 | assertThat(((Player) result.get(1)).firstName()).isEqualTo("Steven"); 203 | assertThat(((Player) result.get(1)).lastName()).isEqualTo("Stamkos"); 204 | assertThat(result.get(3)).isNull(); 205 | } 206 | 207 | @Test 208 | public void testAllNotifications() { 209 | JsonRpcClient client = initClient("all_notifications"); 210 | Map result = client.createBatchRequest() 211 | .add("isAlive") 212 | .add("updateCache", Map.of("name", "assets")) 213 | .add("newSchedule", 0, 2, 0, 0, 0) 214 | .execute(); 215 | assertThat(result).isEmpty(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcClientErrorsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.github.arteam.simplejsonrpc.client.domain.Player; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; 9 | import static org.assertj.core.api.Assertions.assertThatIllegalStateException; 10 | 11 | /** 12 | * Date: 8/17/14 13 | * Time: 5:06 PM 14 | */ 15 | public class JsonRpcClientErrorsTest { 16 | 17 | private JsonRpcClient client = new JsonRpcClient(request -> { 18 | System.out.println(request); 19 | return """ 20 | {"jsonrpc": "2.0", "id": 1001, "result": true} 21 | """; 22 | }); 23 | 24 | @Test 25 | public void testMethodIsNotSet() { 26 | assertThatIllegalArgumentException().isThrownBy(() -> client.createRequest().execute()); 27 | } 28 | 29 | @Test 30 | public void testBadJson() { 31 | client = new JsonRpcClient(request -> { 32 | System.out.println(request); 33 | return "test"; 34 | }); 35 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 36 | .method("update") 37 | .id(1) 38 | .execute()); 39 | } 40 | 41 | @Test 42 | public void testIOError() { 43 | client = new JsonRpcClient(request -> { 44 | throw new IOException("Network is down"); 45 | }); 46 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 47 | .method("update") 48 | .id(1) 49 | .execute()); 50 | } 51 | 52 | @Test 53 | public void testBadProtocolVersion() { 54 | client = new JsonRpcClient(request -> "{\"jsonrpc\": \"1.0\", \"id\": 1001}"); 55 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 56 | .method("update") 57 | .id(1) 58 | .execute()); 59 | } 60 | 61 | @Test 62 | public void notJsonRpc20Response() { 63 | client = new JsonRpcClient(request -> "{\"some\":\"json\"}"); 64 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 65 | .method("update") 66 | .id(1) 67 | .execute()); 68 | } 69 | 70 | @Test 71 | public void testIdIsNotSet() { 72 | client = new JsonRpcClient(request -> "{\"jsonrpc\": \"2.0\"}"); 73 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 74 | .method("update") 75 | .id(1) 76 | .execute()); 77 | } 78 | 79 | @Test 80 | public void testResultAndErrorAreNotSet() { 81 | JsonRpcClient client = new JsonRpcClient(request -> { 82 | System.out.println(request); 83 | return """ 84 | {"jsonrpc": "2.0", "id": 1001} 85 | """; 86 | }); 87 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 88 | .method("update") 89 | .id(1) 90 | .execute()); 91 | } 92 | 93 | @Test 94 | public void testWrongBuilder() { 95 | assertThatIllegalArgumentException().isThrownBy(() -> client.createRequest() 96 | .method("update") 97 | .id(1) 98 | .params(1, 2) 99 | .param("test", "param") 100 | .returnAs(String.class) 101 | .execute()); 102 | } 103 | 104 | @Test 105 | public void testExpectedNotNull() { 106 | JsonRpcClient client = new JsonRpcClient(request -> { 107 | System.out.println(request); 108 | return """ 109 | {"jsonrpc": "2.0", "result" : null, "id": 1001} 110 | """; 111 | }); 112 | assertThatIllegalStateException().isThrownBy(() -> client.createRequest() 113 | .method("getPlayer") 114 | .id(1001) 115 | .returnAs(Player.class) 116 | .execute()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcClientNotificationsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.arteam.simplejsonrpc.core.domain.Request; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; 9 | 10 | /** 11 | * Date: 8/17/14 12 | * Time: 11:54 PM 13 | */ 14 | public class JsonRpcClientNotificationsTest { 15 | 16 | @Test 17 | public void testNotificationObjectParams() { 18 | JsonRpcClient client = new JsonRpcClient(text -> { 19 | System.out.println(text); 20 | ObjectMapper mapper = new ObjectMapper(); 21 | Request request = mapper.readValue(text, Request.class); 22 | assertThat(request.jsonrpc()).isEqualTo("2.0"); 23 | assertThat(request.method()).isEqualTo("update"); 24 | assertThat(request.params()).isEqualTo(mapper.createObjectNode().put("cacheName", "profiles")); 25 | assertThat(request.id().isMissingNode()).isFalse(); 26 | return ""; 27 | }); 28 | 29 | client.createNotification() 30 | .method("update") 31 | .param("cacheName", "profiles") 32 | .execute(); 33 | } 34 | 35 | @Test 36 | public void testNotificationArrayParams() { 37 | JsonRpcClient client = new JsonRpcClient(text -> { 38 | System.out.println(text); 39 | ObjectMapper mapper = new ObjectMapper(); 40 | Request request = mapper.readValue(text, Request.class); 41 | assertThat(request.jsonrpc()).isEqualTo("2.0"); 42 | assertThat(request.method()).isEqualTo("setExpirationTime"); 43 | assertThat(request.params()).isEqualTo(mapper.createArrayNode().add("profiles").add(20)); 44 | assertThat(request.id().isMissingNode()).isFalse(); 45 | return ""; 46 | }); 47 | 48 | client.createNotification() 49 | .method("setExpirationTime") 50 | .params("profiles", 20) 51 | .execute(); 52 | } 53 | 54 | @Test 55 | public void testMethodIsNotSet() { 56 | JsonRpcClient client = new JsonRpcClient(text -> { 57 | System.out.println(text); 58 | return ""; 59 | }); 60 | assertThatIllegalArgumentException().isThrownBy(() -> client.createNotification().execute()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcClientTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 5 | import com.github.arteam.simplejsonrpc.client.domain.Player; 6 | import com.github.arteam.simplejsonrpc.client.domain.Position; 7 | import com.github.arteam.simplejsonrpc.client.domain.Team; 8 | import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException; 9 | import com.github.arteam.simplejsonrpc.client.util.MapBuilder; 10 | import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import java.time.LocalDate; 15 | import java.time.ZoneId; 16 | import java.util.Date; 17 | import java.util.Deque; 18 | import java.util.LinkedHashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Optional; 22 | import java.util.Set; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | 27 | /** 28 | * Date: 8/9/14 29 | * Time: 10:55 PM 30 | */ 31 | public class JsonRpcClientTest extends BaseClientTest { 32 | 33 | @Test 34 | public void testAddPlayer() { 35 | JsonRpcClient client = initClient("add_player"); 36 | Boolean result = client.createRequest() 37 | .id("asd671") 38 | .method("add") 39 | .param("player", new Player("Kevin", "Shattenkirk", 40 | new Team("St. Louis Blues", "NHL"), 22, Position.DEFENDER, 41 | Date.from(LocalDate.parse("1989-01-29").atStartOfDay(ZoneId.of("UTC")).toInstant()), 42 | 4.25)) 43 | .returnAs(Boolean.class) 44 | .execute(); 45 | assertThat(result).isTrue(); 46 | } 47 | 48 | @Test 49 | public void findPlayerByInitials() { 50 | JsonRpcClient client = initClient("find_player"); 51 | Player player = client.createRequest() 52 | .method("findByInitials") 53 | .id(43121) 54 | .param("firstName", "Steven") 55 | .param("lastName", "Stamkos") 56 | .returnAs(Player.class) 57 | .execute(); 58 | assertThat(player).isNotNull(); 59 | assertThat(player.firstName()).isEqualTo("Steven"); 60 | assertThat(player.lastName()).isEqualTo("Stamkos"); 61 | } 62 | 63 | @Test 64 | public void testPlayerIsNotFound() { 65 | JsonRpcClient client = initClient("player_is_not_found"); 66 | Player player = client.createRequest() 67 | .method("findByInitials") 68 | .id(4111L) 69 | .param("firstName", "Vladimir") 70 | .param("lastName", "Sobotka") 71 | .returnAs(Player.class) 72 | .executeNullable(); 73 | assertThat(player).isNull(); 74 | } 75 | 76 | @Test 77 | public void testFindArray() { 78 | JsonRpcClient client = initClient("find_player_array"); 79 | Player player = client.createRequest() 80 | .method("findByInitials") 81 | .id("dsfs1214") 82 | .params("Ben", "Bishop") 83 | .returnAs(Player.class) 84 | .execute(); 85 | assertThat(player).isNotNull(); 86 | assertThat(player.firstName()).isEqualTo("Ben"); 87 | assertThat(player.lastName()).isEqualTo("Bishop"); 88 | } 89 | 90 | @Test 91 | public void testReturnList() { 92 | JsonRpcClient client = initClient("findByBirthYear"); 93 | List players = client.createRequest() 94 | .method("find_by_birth_year") 95 | .id(5621) 96 | .param("birth_year", 1990) 97 | .returnAsList(Player.class) 98 | .execute(); 99 | assertThat(players).isNotNull(); 100 | assertThat(players).hasSize(3); 101 | assertThat(players.get(0).lastName()).isEqualTo("Allen"); 102 | assertThat(players.get(1).lastName()).isEqualTo("Stamkos"); 103 | assertThat(players.get(2).lastName()).isEqualTo("Hedman"); 104 | } 105 | 106 | @Test 107 | public void testReturnSet() { 108 | JsonRpcClient client = initClient("findByBirthYear"); 109 | Set players = client.createRequest() 110 | .method("find_by_birth_year") 111 | .id(5621) 112 | .param("birth_year", 1990) 113 | .returnAsSet(Player.class) 114 | .execute(); 115 | assertThat(players).isNotNull(); 116 | assertThat(players).hasSize(3); 117 | List lastNames = players.stream().map(Player::lastName).toList(); 118 | assertThat(lastNames).containsOnly("Allen", "Stamkos", "Hedman"); 119 | } 120 | 121 | @Test 122 | public void testReturnArray() { 123 | JsonRpcClient client = initClient("findByBirthYear"); 124 | Player[] players = client.createRequest() 125 | .method("find_by_birth_year") 126 | .id(5621) 127 | .param("birth_year", 1990) 128 | .returnAsArray(Player.class) 129 | .execute(); 130 | assertThat(players).isNotNull(); 131 | assertThat(players).hasSize(3); 132 | assertThat(players[0].lastName()).isEqualTo("Allen"); 133 | assertThat(players[1].lastName()).isEqualTo("Stamkos"); 134 | assertThat(players[2].lastName()).isEqualTo("Hedman"); 135 | } 136 | 137 | @Test 138 | public void testReturnCollection() { 139 | JsonRpcClient client = initClient("findByBirthYear"); 140 | Deque players = (Deque) client.createRequest() 141 | .method("find_by_birth_year") 142 | .id(5621) 143 | .param("birth_year", 1990) 144 | .returnAsCollection(Deque.class, Player.class) 145 | .execute(); 146 | assertThat(players).isNotNull(); 147 | assertThat(players).hasSize(3); 148 | assertThat(players.pop().lastName()).isEqualTo("Allen"); 149 | assertThat(players.pop().lastName()).isEqualTo("Stamkos"); 150 | assertThat(players.pop().lastName()).isEqualTo("Hedman"); 151 | } 152 | 153 | @Test 154 | public void testNoParams() { 155 | JsonRpcClient client = initClient("getPlayers"); 156 | List players = client.createRequest() 157 | .method("getPlayers") 158 | .id(1000) 159 | .returnAsList(Player.class) 160 | .execute(); 161 | assertThat(players).isNotNull(); 162 | assertThat(players).hasSize(3); 163 | assertThat(players.get(0).lastName()).isEqualTo("Bishop"); 164 | assertThat(players.get(1).lastName()).isEqualTo("Tarasenko"); 165 | assertThat(players.get(2).lastName()).isEqualTo("Bouwmeester"); 166 | } 167 | 168 | @Test 169 | public void testMap() { 170 | Map contractLengths = new MapBuilder() 171 | .put("Backes", 4) 172 | .put("Tarasenko", 3) 173 | .put("Allen", 2) 174 | .put("Bouwmeester", 5) 175 | .put("Stamkos", 8) 176 | .put("Callahan", 3) 177 | .put("Bishop", 4) 178 | .put("Hedman", 2) 179 | .build(); 180 | JsonRpcClient client = initClient("getContractSums"); 181 | Map contractSums = client.createRequest() 182 | .method("getContractSums") 183 | .id(97555) 184 | .param("contractLengths", contractLengths) 185 | .returnAsMap(LinkedHashMap.class, Double.class) 186 | .execute(); 187 | assertThat(contractSums).isExactlyInstanceOf(LinkedHashMap.class); 188 | assertThat(contractSums).isEqualTo(new MapBuilder() 189 | .put("Backes", 18.0) 190 | .put("Tarasenko", 2.7) 191 | .put("Allen", 1.0) 192 | .put("Bouwmeester", 27.0) 193 | .put("Stamkos", 60.0) 194 | .put("Callahan", 17.4) 195 | .put("Bishop", 9.2) 196 | .put("Hedman", 8.0) 197 | .build()); 198 | } 199 | 200 | @Test 201 | public void testOptional() { 202 | JsonRpcClient client = initClient("player_is_not_found"); 203 | Optional optionalPlayer = client.createRequest() 204 | .method("findByInitials") 205 | .id(4111L) 206 | .param("firstName", "Vladimir") 207 | .param("lastName", "Sobotka") 208 | .returnAs(new TypeReference>() { 209 | }) 210 | .execute(); 211 | assertThat(optionalPlayer.isPresent()).isFalse(); 212 | } 213 | 214 | @Test 215 | public void testJsonRpcError() { 216 | JsonRpcClient client = initClient("methodNotFound"); 217 | try { 218 | client.createRequest() 219 | .method("getPlayer") 220 | .id(1001) 221 | .returnAs(Player.class) 222 | .execute(); 223 | Assertions.fail(); 224 | } catch (JsonRpcException e) { 225 | e.printStackTrace(); 226 | ErrorMessage errorMessage = e.getErrorMessage(); 227 | assertThat(errorMessage.getCode()).isEqualTo(-32601); 228 | assertThat(errorMessage.getMessage()).isEqualTo("Method not found"); 229 | } 230 | } 231 | 232 | @Test 233 | public void testJsonRpcErrorWithDataAttribute() { 234 | JsonRpcClient client = initClient("errorWithDataAttribute"); 235 | try { 236 | client.createRequest() 237 | .method("getErrorWithData") 238 | .id(1002) 239 | .returnAs(Player.class) 240 | .execute(); 241 | Assertions.fail(); 242 | } catch (JsonRpcException e) { 243 | e.printStackTrace(); 244 | ErrorMessage errorMessage = e.getErrorMessage(); 245 | assertThat(errorMessage.getCode()).isEqualTo(-32000); 246 | assertThat(errorMessage.getMessage()).isEqualTo("This is an error with data attribute"); 247 | assertThat(errorMessage.getData()).isEqualTo(JsonNodeFactory.instance.textNode("Error data")); 248 | } 249 | } 250 | 251 | @Test 252 | public void testJsonRpcErrorWithNullDataAttribute() { 253 | JsonRpcClient client = initClient("errorWithNullDataAttribute"); 254 | try { 255 | client.createRequest() 256 | .method("getErrorWithNullData") 257 | .id(1003) 258 | .returnAs(Player.class) 259 | .execute(); 260 | Assertions.fail(); 261 | } catch (JsonRpcException e) { 262 | e.printStackTrace(); 263 | ErrorMessage errorMessage = e.getErrorMessage(); 264 | assertThat(errorMessage.getCode()).isEqualTo(-32000); 265 | assertThat(errorMessage.getMessage()).isEqualTo("This is an error with null data attribute"); 266 | assertThat(errorMessage.getData()).isEqualTo(JsonNodeFactory.instance.nullNode()); 267 | } 268 | } 269 | 270 | @Test 271 | public void testJsonRpcErrorWithStructuredDataAttribute() { 272 | JsonRpcClient client = initClient("errorWithStructuredDataAttribute"); 273 | try { 274 | client.createRequest() 275 | .method("getErrorWithStructuredData") 276 | .id(1003) 277 | .returnAs(Player.class) 278 | .execute(); 279 | Assertions.fail(); 280 | } catch (JsonRpcException e) { 281 | e.printStackTrace(); 282 | ErrorMessage errorMessage = e.getErrorMessage(); 283 | assertThat(errorMessage.getCode()).isEqualTo(-32000); 284 | assertThat(errorMessage.getMessage()).isEqualTo("This is an error with structured data attribute"); 285 | assertThat(errorMessage.getData()).isNotNull(); 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/JsonRpcObjectAPITest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.github.arteam.simplejsonrpc.client.domain.Player; 4 | import com.github.arteam.simplejsonrpc.client.domain.Position; 5 | import com.github.arteam.simplejsonrpc.client.domain.Team; 6 | import com.github.arteam.simplejsonrpc.client.exception.JsonRpcException; 7 | import com.github.arteam.simplejsonrpc.client.object.FixedIntegerIdGenerator; 8 | import com.github.arteam.simplejsonrpc.client.object.FixedStringIdGenerator; 9 | import com.github.arteam.simplejsonrpc.client.object.TeamService; 10 | import com.github.arteam.simplejsonrpc.client.util.MapBuilder; 11 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 12 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 13 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 14 | import com.github.arteam.simplejsonrpc.core.domain.ErrorMessage; 15 | import org.junit.jupiter.api.Assertions; 16 | import org.junit.jupiter.api.Test; 17 | 18 | import java.sql.Date; 19 | import java.time.LocalDate; 20 | import java.time.ZoneOffset; 21 | import java.util.LinkedHashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.zip.Checksum; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | import static org.assertj.core.api.Assertions.assertThatIllegalStateException; 29 | 30 | /** 31 | * Date: 24.08.14 32 | * Time: 18:06 33 | */ 34 | public class JsonRpcObjectAPITest extends BaseClientTest { 35 | 36 | @Test 37 | public void testAddPlayer() { 38 | JsonRpcClient client = initClient("add_player"); 39 | TeamService teamService = client.onDemand(TeamService.class, new FixedStringIdGenerator("asd671")); 40 | boolean result = teamService.add(new Player("Kevin", "Shattenkirk", 41 | new Team("St. Louis Blues", "NHL"), 22, Position.DEFENDER, 42 | Date.from(LocalDate.parse("1989-01-29").atStartOfDay(ZoneOffset.UTC).toInstant()), 43 | 4.25)); 44 | assertThat(result).isTrue(); 45 | } 46 | 47 | @Test 48 | public void findPlayerByInitials() { 49 | JsonRpcClient client = initClient("find_player"); 50 | Player player = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(43121)).findByInitials("Steven", "Stamkos"); 51 | assertThat(player).isNotNull(); 52 | assertThat(player.firstName()).isEqualTo("Steven"); 53 | assertThat(player.lastName()).isEqualTo("Stamkos"); 54 | } 55 | 56 | @Test 57 | public void testPlayerIsNotFound() { 58 | JsonRpcClient client = initClient("player_is_not_found"); 59 | Player player = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(4111)).findByInitials("Vladimir", "Sobotka"); 60 | assertThat(player).isNull(); 61 | } 62 | 63 | @Test 64 | public void testOptionalParams() { 65 | JsonRpcClient client = initClient("optional_params"); 66 | List players = client.onDemand(TeamService.class, new FixedStringIdGenerator("xar331")) 67 | .find(null, 91, Optional.of(new Team("St. Louis Blues", "NHL")), 68 | null, null, null, Optional.empty()); 69 | Assertions.assertEquals(players.size(), 1); 70 | Player player = players.get(0); 71 | assertThat(player.team()).isEqualTo(new Team("St. Louis Blues", "NHL")); 72 | assertThat(player.number()).isEqualTo(91); 73 | assertThat(player.firstName()).isEqualTo("Vladimir"); 74 | assertThat(player.lastName()).isEqualTo("Tarasenko"); 75 | } 76 | 77 | @Test 78 | public void testOptionalArray() { 79 | JsonRpcClient client = initClient("find_array_null_params"); 80 | List players = client.onDemand(TeamService.class, ParamsType.ARRAY, new FixedStringIdGenerator("pasd81")) 81 | .find(null, 19, Optional.of(new Team("St. Louis Blues", "NHL")), 82 | null, null, null, Optional.empty()); 83 | Assertions.assertEquals(players.size(), 1); 84 | Player player = players.get(0); 85 | assertThat(player.team()).isEqualTo(new Team("St. Louis Blues", "NHL")); 86 | assertThat(player.number()).isEqualTo(19); 87 | assertThat(player.firstName()).isEqualTo("Jay"); 88 | assertThat(player.lastName()).isEqualTo("Bouwmeester"); 89 | } 90 | 91 | @Test 92 | public void testFindArray() { 93 | JsonRpcClient client = initClient("find_player_array"); 94 | Player player = client.onDemand(TeamService.class, ParamsType.ARRAY, new FixedStringIdGenerator("dsfs1214")) 95 | .findByInitials("Ben", "Bishop"); 96 | assertThat(player).isNotNull(); 97 | assertThat(player.firstName()).isEqualTo("Ben"); 98 | assertThat(player.lastName()).isEqualTo("Bishop"); 99 | } 100 | 101 | @Test 102 | public void testReturnList() { 103 | JsonRpcClient client = initClient("findByBirthYear"); 104 | List players = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(5621)).findByBirthYear(1990); 105 | assertThat(players).isNotNull(); 106 | assertThat(players).hasSize(3); 107 | assertThat(players.get(0).lastName()).isEqualTo("Allen"); 108 | assertThat(players.get(1).lastName()).isEqualTo("Stamkos"); 109 | assertThat(players.get(2).lastName()).isEqualTo("Hedman"); 110 | } 111 | 112 | @Test 113 | public void testNoParams() { 114 | JsonRpcClient client = initClient("getPlayers"); 115 | List players = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(1000)).getPlayers(); 116 | assertThat(players).isNotNull(); 117 | assertThat(players).hasSize(3); 118 | assertThat(players.get(0).lastName()).isEqualTo("Bishop"); 119 | assertThat(players.get(1).lastName()).isEqualTo("Tarasenko"); 120 | assertThat(players.get(2).lastName()).isEqualTo("Bouwmeester"); 121 | } 122 | 123 | @Test 124 | public void testReturnVoid() { 125 | JsonRpcClient client = initClient("logout"); 126 | client.onDemand(TeamService.class, new FixedIntegerIdGenerator(29314)).logout("fgt612"); 127 | } 128 | 129 | @Test 130 | public void testMap() { 131 | Map contractLengths = new MapBuilder() 132 | .put("Backes", 4) 133 | .put("Tarasenko", 3) 134 | .put("Allen", 2) 135 | .put("Bouwmeester", 5) 136 | .put("Stamkos", 8) 137 | .put("Callahan", 3) 138 | .put("Bishop", 4) 139 | .put("Hedman", 2) 140 | .build(); 141 | JsonRpcClient client = initClient("getContractSums"); 142 | Map contractSums = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(97555)) 143 | .getContractSums(contractLengths); 144 | assertThat(contractSums).isExactlyInstanceOf(LinkedHashMap.class); 145 | assertThat(contractSums).isEqualTo(new MapBuilder() 146 | .put("Backes", 18.0) 147 | .put("Tarasenko", 2.7) 148 | .put("Allen", 1.0) 149 | .put("Bouwmeester", 27.0) 150 | .put("Stamkos", 60.0) 151 | .put("Callahan", 17.4) 152 | .put("Bishop", 9.2) 153 | .put("Hedman", 8.0) 154 | .build()); 155 | } 156 | 157 | @Test 158 | public void testOptional() { 159 | JsonRpcClient client = initClient("player_is_not_found"); 160 | Optional optionalPlayer = client.onDemand(TeamService.class, new FixedIntegerIdGenerator(4111)) 161 | .optionalFindByInitials("Vladimir", "Sobotka"); 162 | assertThat(optionalPlayer.isPresent()).isFalse(); 163 | } 164 | 165 | @Test 166 | public void testJsonRpcError() { 167 | JsonRpcClient client = initClient("methodNotFound"); 168 | try { 169 | client.onDemand(TeamService.class, new FixedIntegerIdGenerator(1001)).getPlayer(); 170 | Assertions.fail(); 171 | } catch (JsonRpcException e) { 172 | e.printStackTrace(); 173 | ErrorMessage errorMessage = e.getErrorMessage(); 174 | assertThat(errorMessage.getCode()).isEqualTo(-32601); 175 | assertThat(errorMessage.getMessage()).isEqualTo("Method not found"); 176 | } 177 | } 178 | 179 | @JsonRpcService 180 | interface BogusTeamService { 181 | 182 | @JsonRpcMethod 183 | void bogusLogin(String username, @JsonRpcParam("password") String password); 184 | } 185 | 186 | @Test 187 | public void testParameterIsNotAnnotated() { 188 | assertThatIllegalStateException() 189 | .isThrownBy(() -> fakeClient().onDemand(BogusTeamService.class).bogusLogin("super", "secret")) 190 | .withMessage("Parameter with index=0 of method 'bogusLogin' is not annotated with @JsonRpcParam"); 191 | } 192 | 193 | @SuppressWarnings({"EqualsBetweenInconvertibleTypes", "ResultOfMethodCallIgnored"}) 194 | @Test 195 | public void testNotJsonRpcMethod() { 196 | assertThatIllegalStateException() 197 | .isThrownBy(() -> fakeClient().onDemand(TeamService.class).equals("Test")) 198 | .withMessage("Method 'equals' is not JSON-RPC available"); 199 | } 200 | 201 | @JsonRpcService 202 | public interface MethodIsNotAnnotatedService { 203 | 204 | boolean find(@JsonRpcParam("name") String name); 205 | } 206 | 207 | @Test 208 | public void testMethodIsNotAnnotated() { 209 | assertThatIllegalStateException() 210 | .isThrownBy(() -> fakeClient().onDemand(MethodIsNotAnnotatedService.class).find("Logan")) 211 | .withMessage("Method 'find' is not annotated as @JsonRpcMethod"); 212 | } 213 | 214 | @Test 215 | public void testServiceIsNotAnnotated() { 216 | assertThatIllegalStateException() 217 | .isThrownBy(() -> fakeClient().onDemand(Checksum.class).getValue()) 218 | .withMessage("Class 'java.util.zip.Checksum' is not annotated as @JsonRpcService"); 219 | } 220 | 221 | @JsonRpcService 222 | interface DuplicateParametersService { 223 | 224 | @JsonRpcMethod 225 | boolean find(@JsonRpcParam("code") String username, @JsonRpcParam("code") String code, @JsonRpcParam("number") int number); 226 | } 227 | 228 | @Test 229 | public void testDuplicatedParameterNames() { 230 | assertThatIllegalStateException() 231 | .isThrownBy(() -> fakeClient().onDemand(DuplicateParametersService.class).find("Joe", "12", 21)) 232 | .withMessage("Two parameters of method 'find' have the same name 'code'"); 233 | } 234 | 235 | } 236 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/RequestResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | 6 | /** 7 | * Date: 8/9/14 8 | * Time: 10:59 PM 9 | */ 10 | class RequestResponse { 11 | 12 | @JsonProperty 13 | public JsonNode request; 14 | 15 | @JsonProperty 16 | public JsonNode response; 17 | } 18 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/domain/Player.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.domain; 2 | 3 | import java.util.Date; 4 | 5 | public record Player(String firstName, String lastName, 6 | Team team, int number, 7 | Position position, Date birthDate, 8 | double capHit) { 9 | } 10 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/domain/Position.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | 6 | public enum Position { 7 | GOALTENDER("G"), DEFENDER("D"), RIGHT_WINGER("RW"), LEFT_WINGER("LW"), CENTER("C"); 8 | 9 | private static final Position[] VALUES = values(); 10 | 11 | private final String code; 12 | 13 | Position(String code) { 14 | this.code = code; 15 | } 16 | 17 | @JsonValue 18 | public String getCode() { 19 | return code; 20 | } 21 | 22 | @JsonCreator 23 | public static Position byCode(String code) { 24 | for (Position position : VALUES) { 25 | if (position.code.equalsIgnoreCase(code)) { 26 | return position; 27 | } 28 | } 29 | throw new IllegalStateException("Unable find Position by code=" + code); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/domain/Team.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.domain; 2 | 3 | public record Team(String name, String league) { 4 | } 5 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/generator/AtomicLongIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.arteam.simplejsonrpc.client.JsonRpcClient; 6 | import com.github.arteam.simplejsonrpc.client.object.TeamService; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.BitSet; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class AtomicLongIdGeneratorTest { 16 | 17 | @Test 18 | public void test() throws Exception { 19 | final ObjectMapper mapper = new ObjectMapper(); 20 | final BitSet numbers = new BitSet(100); 21 | JsonRpcClient client = new JsonRpcClient(request -> { 22 | System.out.println(request); 23 | JsonNode jsonNode = mapper.readTree(request); 24 | long id = jsonNode.get("id").asLong(); 25 | System.out.println("id=" + id); 26 | synchronized (numbers) { 27 | numbers.set((int) id, true); 28 | } 29 | return mapper.writeValueAsString(mapper.createObjectNode() 30 | .put("jsonrpc", "2.0") 31 | .putNull("result") 32 | .put("id", id)); 33 | }); 34 | final TeamService teamService = client.onDemand(TeamService.class, new AtomicLongIdGenerator()); 35 | ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 36 | for (int i = 0; i < 100; i++) { 37 | executor.submit(() -> { 38 | teamService.findByCapHit(5.5); 39 | }); 40 | } 41 | executor.shutdown(); 42 | Assertions.assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS)); 43 | Assertions.assertEquals(numbers.cardinality(), 100); 44 | } 45 | } -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomIntIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | public class SecureRandomIntIdGeneratorTest { 9 | 10 | @Test 11 | public void testDefaultLimit() { 12 | SecureRandomIntIdGenerator generator = new SecureRandomIntIdGenerator(); 13 | Integer value = generator.generate(); 14 | assertTrue(value >= 0); 15 | assertTrue(value < 65536); 16 | } 17 | 18 | @Test 19 | public void testGenerate() throws Exception { 20 | int limit = 1000000; 21 | SecureRandomIntIdGenerator generator = new SecureRandomIntIdGenerator(limit); 22 | int amount = 100; 23 | for (int i = 0; i < amount; i++) { 24 | Integer value = generator.generate(); 25 | assertTrue(value >= 0); 26 | assertTrue(value < limit); 27 | } 28 | } 29 | 30 | @Test 31 | public void testPositiveLimit() { 32 | assertThrows(IllegalArgumentException.class, 33 | () -> new SecureRandomIntIdGenerator(-1)); 34 | } 35 | } -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomLongIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | public class SecureRandomLongIdGeneratorTest { 10 | 11 | @Test 12 | public void testGenerate() { 13 | SecureRandomLongIdGenerator generator = new SecureRandomLongIdGenerator(); 14 | int amount = 100; 15 | Set ids = new HashSet<>(amount * 2); 16 | for (int i = 0; i < amount; i++) { 17 | Long id = generator.generate(); 18 | Assertions.assertTrue(id > 0); 19 | System.out.println(id); 20 | ids.add(id); 21 | } 22 | Assertions.assertEquals(ids.size(), amount); 23 | } 24 | } -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/generator/SecureRandomStringIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.generator; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | public class SecureRandomStringIdGeneratorTest { 11 | 12 | private static final char[] ALPHABET = "0123456789abcdef".toCharArray(); 13 | private SecureRandomStringIdGenerator secureRandomStringIdGenerator; 14 | 15 | @Test 16 | public void test() { 17 | secureRandomStringIdGenerator = new SecureRandomStringIdGenerator(); 18 | testSize(40); 19 | } 20 | 21 | @Test 22 | public void testSpecificSize() { 23 | secureRandomStringIdGenerator = new SecureRandomStringIdGenerator(64); 24 | testSize(64); 25 | } 26 | 27 | private void testSize(int size) { 28 | int amount = 100; 29 | Set ids = new HashSet<>(amount * 2); 30 | for (int i = 0; i < amount; i++) { 31 | String id = secureRandomStringIdGenerator.generate(); 32 | System.out.println(id); 33 | Assertions.assertEquals(id.length(), size); 34 | for (char c : id.toCharArray()) { 35 | if (Arrays.binarySearch(ALPHABET, c) == -1) { 36 | Assertions.fail("Bad symbol: " + c); 37 | } 38 | } 39 | ids.add(id); 40 | } 41 | Assertions.assertEquals(ids.size(), amount); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/object/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.object; 2 | 3 | import com.github.arteam.simplejsonrpc.client.JsonRpcParams; 4 | import com.github.arteam.simplejsonrpc.client.ParamsType; 5 | import com.github.arteam.simplejsonrpc.client.domain.Player; 6 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 7 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 8 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Date: 11/17/14 14 | * Time: 9:59 PM 15 | */ 16 | @JsonRpcService 17 | public interface BaseService { 18 | 19 | @JsonRpcMethod 20 | @JsonRpcParams(ParamsType.ARRAY) 21 | List getPlayers(); 22 | 23 | @JsonRpcMethod 24 | @JsonRpcParams(ParamsType.ARRAY) 25 | Player getPlayer(); 26 | 27 | @JsonRpcMethod 28 | long login(@JsonRpcParam("login") String login, @JsonRpcParam("password") String password); 29 | 30 | @JsonRpcMethod 31 | void logout(@JsonRpcParam("token") String token); 32 | } 33 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/object/FixedIntegerIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.object; 2 | 3 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 4 | 5 | /** 6 | * Date: 24.08.14 7 | * Time: 18:43 8 | */ 9 | public class FixedIntegerIdGenerator implements IdGenerator { 10 | 11 | private final Integer value; 12 | 13 | public FixedIntegerIdGenerator(Integer value) { 14 | this.value = value; 15 | } 16 | 17 | @Override 18 | public Integer generate() { 19 | return value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/object/FixedStringIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.object; 2 | 3 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 4 | 5 | /** 6 | * Date: 24.08.14 7 | * Time: 18:41 8 | */ 9 | public class FixedStringIdGenerator implements IdGenerator { 10 | 11 | private final String value; 12 | 13 | public FixedStringIdGenerator(String value) { 14 | this.value = value; 15 | } 16 | 17 | @Override 18 | public String generate() { 19 | return value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/object/TeamService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.object; 2 | 3 | import com.github.arteam.simplejsonrpc.client.JsonRpcId; 4 | import com.github.arteam.simplejsonrpc.client.JsonRpcParams; 5 | import com.github.arteam.simplejsonrpc.client.ParamsType; 6 | import com.github.arteam.simplejsonrpc.client.domain.Player; 7 | import com.github.arteam.simplejsonrpc.client.domain.Position; 8 | import com.github.arteam.simplejsonrpc.client.domain.Team; 9 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 10 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional; 11 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 12 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.util.Date; 16 | import java.util.LinkedHashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Optional; 20 | 21 | /** 22 | * Date: 24.08.14 23 | * Time: 18:02 24 | */ 25 | @JsonRpcService 26 | @JsonRpcId(TestIdGenerator.class) 27 | @JsonRpcParams(ParamsType.MAP) 28 | public interface TeamService extends BaseService { 29 | 30 | @JsonRpcMethod 31 | boolean add(@JsonRpcParam("player") Player s); 32 | 33 | @JsonRpcMethod("find_by_birth_year") 34 | List findByBirthYear(@JsonRpcParam("birth_year") int birthYear); 35 | 36 | @JsonRpcMethod 37 | Player findByInitials(@JsonRpcParam("firstName") String firstName, 38 | @JsonRpcParam("lastName") String lastName); 39 | 40 | @JsonRpcMethod("findByInitials") 41 | Optional optionalFindByInitials(@JsonRpcParam("firstName") String firstName, 42 | @JsonRpcParam("lastName") String lastName); 43 | 44 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 45 | @JsonRpcMethod 46 | List find(@JsonRpcOptional @JsonRpcParam("position") @Nullable Position position, 47 | @JsonRpcOptional @JsonRpcParam("number") int number, 48 | @JsonRpcOptional @JsonRpcParam("team") Optional team, 49 | @JsonRpcOptional @JsonRpcParam("firstName") @Nullable String firstName, 50 | @JsonRpcOptional @JsonRpcParam("lastName") @Nullable String lastName, 51 | @JsonRpcOptional @JsonRpcParam("birthDate") @Nullable Date birthDate, 52 | @JsonRpcOptional @JsonRpcParam("capHit") Optional capHit); 53 | 54 | @JsonRpcMethod 55 | Player findByCapHit(@JsonRpcParam("cap") double capHit); 56 | 57 | @JsonRpcMethod 58 | List findPlayersByFirstNames(@JsonRpcParam("names") List names); 59 | 60 | @JsonRpcMethod 61 | List findPlayersByNumbers(@JsonRpcParam("numbers") int... numbers); 62 | 63 | @JsonRpcMethod 64 | List genericFindPlayersByNumbers(@JsonRpcParam("numbers") final T... numbers); 65 | 66 | @JsonRpcMethod 67 | LinkedHashMap getContractSums(@JsonRpcParam("contractLengths") Map contractLengths); 68 | } 69 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/object/TestIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.object; 2 | 3 | import com.github.arteam.simplejsonrpc.client.generator.IdGenerator; 4 | 5 | /** 6 | * Date: 24.08.14 7 | * Time: 18:24 8 | */ 9 | public class TestIdGenerator implements IdGenerator { 10 | 11 | private static String id; 12 | 13 | @Override 14 | public String generate() { 15 | return "asd671"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/arteam/simplejsonrpc/client/util/MapBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.client.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class MapBuilder { 7 | 8 | private final Map map = new HashMap<>(); 9 | 10 | public MapBuilder put(K k, V v) { 11 | map.put(k, v); 12 | return this; 13 | } 14 | 15 | public Map build() { 16 | return map; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /client/src/test/resources/batch_requests.json: -------------------------------------------------------------------------------- 1 | { 2 | "batch": { 3 | "request": [ 4 | { 5 | "jsonrpc": "2.0", 6 | "method": "findByInitials", 7 | "params": { 8 | "firstName": "Steven", 9 | "lastName": "Stamkos" 10 | }, 11 | "id": "43121" 12 | }, 13 | { 14 | "jsonrpc": "2.0", 15 | "method": "findByInitials", 16 | "params": { 17 | "firstName": "Jack", 18 | "lastName": "Allen" 19 | }, 20 | "id": "43122" 21 | }, 22 | { 23 | "jsonrpc": "2.0", 24 | "method": "findByInitials", 25 | "params": { 26 | "firstName": "Vladimir", 27 | "lastName": "Sobotka" 28 | }, 29 | "id": "43123" 30 | } 31 | ], 32 | "response": [ 33 | { 34 | "jsonrpc": "2.0", 35 | "id": "43121", 36 | "result": { 37 | "firstName": "Steven", 38 | "lastName": "Stamkos", 39 | "team": { 40 | "name": "Tampa Bay Lightning", 41 | "league": "NHL" 42 | }, 43 | "number": 91, 44 | "position": "C", 45 | "birthDate": "1990-02-07T00:00:00.000+00:00", 46 | "capHit": 7.5 47 | } 48 | }, 49 | { 50 | "jsonrpc": "2.0", 51 | "id": "43122", 52 | "result": { 53 | "firstName": "Jack", 54 | "lastName": "Allen", 55 | "team": { 56 | "name": "St. Louis Blues", 57 | "league": "NHL" 58 | }, 59 | "number": 34, 60 | "position": "G", 61 | "birthDate": "1990-08-07T00:00:00.000+00:00", 62 | "capHit": 0.5 63 | } 64 | }, 65 | { 66 | "jsonrpc": "2.0", 67 | "id": "43123", 68 | "result": null 69 | } 70 | ] 71 | }, 72 | "batch_array": { 73 | "request": [ 74 | { 75 | "jsonrpc": "2.0", 76 | "method": "findByInitials", 77 | "params": [ 78 | "Steven", 79 | "Stamkos" 80 | ], 81 | "id": "43121" 82 | }, 83 | { 84 | "jsonrpc": "2.0", 85 | "method": "findByInitials", 86 | "params": [ 87 | "Jack", 88 | "Allen" 89 | ], 90 | "id": "43122" 91 | }, 92 | { 93 | "jsonrpc": "2.0", 94 | "method": "findByInitials", 95 | "params": [ 96 | "Vladimir", 97 | "Sobotka" 98 | ], 99 | "id": "43123" 100 | } 101 | ], 102 | "response": [ 103 | { 104 | "jsonrpc": "2.0", 105 | "id": "43121", 106 | "result": { 107 | "firstName": "Steven", 108 | "lastName": "Stamkos", 109 | "team": { 110 | "name": "Tampa Bay Lightning", 111 | "league": "NHL" 112 | }, 113 | "number": 91, 114 | "position": "C", 115 | "birthDate": "1990-02-07T00:00:00.000+00:00", 116 | "capHit": 7.5 117 | } 118 | }, 119 | { 120 | "jsonrpc": "2.0", 121 | "id": "43122", 122 | "result": { 123 | "firstName": "Jack", 124 | "lastName": "Allen", 125 | "team": { 126 | "name": "St. Louis Blues", 127 | "league": "NHL" 128 | }, 129 | "number": 34, 130 | "position": "G", 131 | "birthDate": "1990-08-07T00:00:00.000+00:00", 132 | "capHit": 0.5 133 | } 134 | }, 135 | { 136 | "jsonrpc": "2.0", 137 | "id": "43123", 138 | "result": null 139 | } 140 | ] 141 | }, 142 | "different_requests": { 143 | "request": [ 144 | { 145 | "jsonrpc": "2.0", 146 | "method": "isAlive", 147 | "params": {}, 148 | "id": 12000 149 | }, 150 | { 151 | "jsonrpc": "2.0", 152 | "method": "findByInitials", 153 | "params": [ 154 | "Kevin", 155 | "Shattenkirk" 156 | ], 157 | "id": 12001 158 | }, 159 | { 160 | "jsonrpc": "2.0", 161 | "method": "find_by_birth_year", 162 | "params": { 163 | "birth_year": 1990 164 | }, 165 | "id": 12002 166 | } 167 | ], 168 | "response": [ 169 | { 170 | "jsonrpc": "2.0", 171 | "id": 12002, 172 | "result": [ 173 | { 174 | "firstName": "Jack", 175 | "lastName": "Allen", 176 | "team": { 177 | "name": "St. Louis Blues", 178 | "league": "NHL" 179 | }, 180 | "number": 34, 181 | "position": "G", 182 | "birthDate": "1990-08-07T00:00:00.000+00:00", 183 | "capHit": 0.5 184 | }, 185 | { 186 | "firstName": "Steven", 187 | "lastName": "Stamkos", 188 | "team": { 189 | "name": "Tampa Bay Lightning", 190 | "league": "NHL" 191 | }, 192 | "number": 91, 193 | "position": "C", 194 | "birthDate": "1990-02-07T00:00:00.000+00:00", 195 | "capHit": 7.5 196 | }, 197 | { 198 | "firstName": "Victor", 199 | "lastName": "Hedman", 200 | "team": { 201 | "name": "Tampa Bay Lightning", 202 | "league": "NHL" 203 | }, 204 | "number": 77, 205 | "position": "D", 206 | "birthDate": "1990-12-18T00:00:00.000+00:00", 207 | "capHit": 4.0 208 | } 209 | ] 210 | }, 211 | { 212 | "jsonrpc": "2.0", 213 | "id": 12000, 214 | "result": true 215 | }, 216 | { 217 | "jsonrpc": "2.0", 218 | "id": 12001, 219 | "result": { 220 | "firstName": "Kevin", 221 | "lastName": "Shattenkirk", 222 | "team": { 223 | "name": "St. Louis Blues", 224 | "league": "NHL" 225 | }, 226 | "number": 22, 227 | "position": "D", 228 | "birthDate": "1989-01-29T00:00:00.000+00:00", 229 | "capHit": 4.25 230 | } 231 | } 232 | ] 233 | }, 234 | "batch_with_notification": { 235 | "request": [ 236 | { 237 | "jsonrpc": "2.0", 238 | "method": "findByInitials", 239 | "params": { 240 | "firstName": "Steven", 241 | "lastName": "Stamkos" 242 | }, 243 | "id": 1 244 | }, 245 | { 246 | "jsonrpc": "2.0", 247 | "method": "updateCache", 248 | "params": [] 249 | }, 250 | { 251 | "jsonrpc": "2.0", 252 | "method": "findByInitials", 253 | "params": [ 254 | "Vladimir", 255 | "Sobotka" 256 | ], 257 | "id": 2 258 | } 259 | ], 260 | "response": [ 261 | { 262 | "jsonrpc": "2.0", 263 | "id": 1, 264 | "result": { 265 | "firstName": "Steven", 266 | "lastName": "Stamkos", 267 | "team": { 268 | "name": "Tampa Bay Lightning", 269 | "league": "NHL" 270 | }, 271 | "number": 91, 272 | "position": "C", 273 | "birthDate": "1990-02-07T00:00:00.000+00:00", 274 | "capHit": 7.5 275 | } 276 | }, 277 | { 278 | "jsonrpc": "2.0", 279 | "id": 2, 280 | "result": null 281 | } 282 | ] 283 | }, 284 | "all_notifications": { 285 | "request": [ 286 | { 287 | "jsonrpc": "2.0", 288 | "method": "isAlive", 289 | "params": [] 290 | }, 291 | { 292 | "jsonrpc": "2.0", 293 | "method": "updateCache", 294 | "params": { 295 | "name": "assets" 296 | } 297 | }, 298 | { 299 | "jsonrpc": "2.0", 300 | "method": "newSchedule", 301 | "params": [ 302 | 0, 303 | 2, 304 | 0, 305 | 0, 306 | 0 307 | ] 308 | } 309 | ], 310 | "response": "" 311 | } 312 | } -------------------------------------------------------------------------------- /client/src/test/resources/client_test_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "add_player": { 3 | "request": { 4 | "jsonrpc": "2.0", 5 | "method": "add", 6 | "params": { 7 | "player": { 8 | "firstName": "Kevin", 9 | "lastName": "Shattenkirk", 10 | "team": { 11 | "name": "St. Louis Blues", 12 | "league": "NHL" 13 | }, 14 | "number": 22, 15 | "position": "D", 16 | "birthDate": "1989-01-29T00:00:00.000+00:00", 17 | "capHit": 4.25 18 | } 19 | }, 20 | "id": "asd671" 21 | }, 22 | "response": { 23 | "jsonrpc": "2.0", 24 | "id": "asd671", 25 | "result": true 26 | } 27 | }, 28 | "find_shattenkirk": { 29 | "request": { 30 | "jsonrpc": "2.0", 31 | "method": "findByInitials", 32 | "params": { 33 | "firstName": "Kevin", 34 | "lastName": "Shattenkirk" 35 | }, 36 | "id": "92739" 37 | }, 38 | "response": { 39 | "jsonrpc": "2.0", 40 | "id": "92739", 41 | "result": { 42 | "firstName": "Kevin", 43 | "lastName": "Shattenkirk", 44 | "team": { 45 | "name": "St. Louis Blues", 46 | "league": "NHL" 47 | }, 48 | "number": 22, 49 | "position": "D", 50 | "birthDate": "1989-01-29T00:00:00.000+00:00", 51 | "capHit": 4.25 52 | } 53 | } 54 | }, 55 | "find_player": { 56 | "request": { 57 | "jsonrpc": "2.0", 58 | "method": "findByInitials", 59 | "params": { 60 | "firstName": "Steven", 61 | "lastName": "Stamkos" 62 | }, 63 | "id": 43121 64 | }, 65 | "response": { 66 | "jsonrpc": "2.0", 67 | "id": 43121, 68 | "result": { 69 | "firstName": "Steven", 70 | "lastName": "Stamkos", 71 | "team": { 72 | "name": "Tampa Bay Lightning", 73 | "league": "NHL" 74 | }, 75 | "number": 91, 76 | "position": "C", 77 | "birthDate": "1990-02-07T00:00:00.000+00:00", 78 | "capHit": 7.5 79 | } 80 | } 81 | }, 82 | "player_is_not_found": { 83 | "request": { 84 | "jsonrpc": "2.0", 85 | "method": "findByInitials", 86 | "params": { 87 | "firstName": "Vladimir", 88 | "lastName": "Sobotka" 89 | }, 90 | "id": 4111 91 | }, 92 | "response": { 93 | "jsonrpc": "2.0", 94 | "id": 4111, 95 | "result": null 96 | } 97 | }, 98 | "find_player_array": { 99 | "request": { 100 | "jsonrpc": "2.0", 101 | "method": "findByInitials", 102 | "params": [ 103 | "Ben", 104 | "Bishop" 105 | ], 106 | "id": "dsfs1214" 107 | }, 108 | "response": { 109 | "jsonrpc": "2.0", 110 | "id": "dsfs1214", 111 | "result": { 112 | "firstName": "Ben", 113 | "lastName": "Bishop", 114 | "team": { 115 | "name": "Tampa Bay Lightning", 116 | "league": "NHL" 117 | }, 118 | "number": 30, 119 | "position": "G", 120 | "birthDate": "1986-11-21T00:00:00.000+00:00", 121 | "capHit": 2.3 122 | } 123 | } 124 | }, 125 | "optional_params": { 126 | "request": { 127 | "jsonrpc": "2.0", 128 | "method": "find", 129 | "params": { 130 | "team": { 131 | "name": "St. Louis Blues", 132 | "league": "NHL" 133 | }, 134 | "number": 91 135 | }, 136 | "id": "xar331" 137 | }, 138 | "response": { 139 | "jsonrpc": "2.0", 140 | "id": "xar331", 141 | "result": [ 142 | { 143 | "firstName": "Vladimir", 144 | "lastName": "Tarasenko", 145 | "team": { 146 | "name": "St. Louis Blues", 147 | "league": "NHL" 148 | }, 149 | "number": 91, 150 | "position": "RW", 151 | "birthDate": "1991-12-13T00:00:00.000+00:00", 152 | "capHit": 0.9 153 | } 154 | ] 155 | } 156 | }, 157 | "find_array_null_params": { 158 | "request": { 159 | "jsonrpc": "2.0", 160 | "method": "find", 161 | "params": [ 162 | null, 163 | 19, 164 | { 165 | "name": "St. Louis Blues", 166 | "league": "NHL" 167 | }, 168 | null, 169 | null, 170 | null, 171 | null 172 | ], 173 | "id": "pasd81" 174 | }, 175 | "response": { 176 | "jsonrpc": "2.0", 177 | "id": "pasd81", 178 | "result": [ 179 | { 180 | "firstName": "Jay", 181 | "lastName": "Bouwmeester", 182 | "team": { 183 | "name": "St. Louis Blues", 184 | "league": "NHL" 185 | }, 186 | "number": 19, 187 | "position": "D", 188 | "birthDate": "1985-08-07T00:00:00.000+00:00", 189 | "capHit": 5.4 190 | } 191 | ] 192 | } 193 | }, 194 | "findByBirthYear": { 195 | "request": { 196 | "jsonrpc": "2.0", 197 | "method": "find_by_birth_year", 198 | "params": { 199 | "birth_year": 1990 200 | }, 201 | "id": 5621 202 | }, 203 | "response": { 204 | "jsonrpc": "2.0", 205 | "id": 5621, 206 | "result": [ 207 | { 208 | "firstName": "Jack", 209 | "lastName": "Allen", 210 | "team": { 211 | "name": "St. Louis Blues", 212 | "league": "NHL" 213 | }, 214 | "number": 34, 215 | "position": "G", 216 | "birthDate": "1990-08-07T00:00:00.000+00:00", 217 | "capHit": 0.5 218 | }, 219 | { 220 | "firstName": "Steven", 221 | "lastName": "Stamkos", 222 | "team": { 223 | "name": "Tampa Bay Lightning", 224 | "league": "NHL" 225 | }, 226 | "number": 91, 227 | "position": "C", 228 | "birthDate": "1990-02-07T00:00:00.000+00:00", 229 | "capHit": 7.5 230 | }, 231 | { 232 | "firstName": "Victor", 233 | "lastName": "Hedman", 234 | "team": { 235 | "name": "Tampa Bay Lightning", 236 | "league": "NHL" 237 | }, 238 | "number": 77, 239 | "position": "D", 240 | "birthDate": "1990-12-18T00:00:00.000+00:00", 241 | "capHit": 4.0 242 | } 243 | ] 244 | } 245 | }, 246 | "getPlayers": { 247 | "request": { 248 | "jsonrpc": "2.0", 249 | "method": "getPlayers", 250 | "params": [], 251 | "id": 1000 252 | }, 253 | "response": { 254 | "jsonrpc": "2.0", 255 | "id": 1000, 256 | "result": [ 257 | { 258 | "firstName": "Ben", 259 | "lastName": "Bishop", 260 | "team": { 261 | "name": "Tampa Bay Lightning", 262 | "league": "NHL" 263 | }, 264 | "number": 30, 265 | "position": "G", 266 | "birthDate": "1986-11-21T00:00:00.000+00:00", 267 | "capHit": 2.3 268 | }, 269 | { 270 | "firstName": "Vladimir", 271 | "lastName": "Tarasenko", 272 | "team": { 273 | "name": "St. Louis Blues", 274 | "league": "NHL" 275 | }, 276 | "number": 91, 277 | "position": "RW", 278 | "birthDate": "1991-12-13T00:00:00.000+00:00", 279 | "capHit": 0.9 280 | }, 281 | { 282 | "firstName": "Jay", 283 | "lastName": "Bouwmeester", 284 | "team": { 285 | "name": "St. Louis Blues", 286 | "league": "NHL" 287 | }, 288 | "number": 19, 289 | "position": "D", 290 | "birthDate": "1985-08-07T00:00:00.000+00:00", 291 | "capHit": 5.4 292 | } 293 | ] 294 | } 295 | }, 296 | "getContractSums": { 297 | "request": { 298 | "jsonrpc": "2.0", 299 | "method": "getContractSums", 300 | "params": { 301 | "contractLengths": { 302 | "Backes": 4, 303 | "Tarasenko": 3, 304 | "Allen": 2, 305 | "Bouwmeester": 5, 306 | "Stamkos": 8, 307 | "Callahan": 3, 308 | "Bishop": 4, 309 | "Hedman": 2 310 | } 311 | }, 312 | "id": 97555 313 | }, 314 | "response": { 315 | "jsonrpc": "2.0", 316 | "id": 97555, 317 | "result": { 318 | "Backes": 18.0, 319 | "Tarasenko": 2.7, 320 | "Allen": 1.0, 321 | "Bouwmeester": 27.0, 322 | "Stamkos": 60.0, 323 | "Callahan": 17.4, 324 | "Bishop": 9.2, 325 | "Hedman": 8.0 326 | } 327 | } 328 | }, 329 | "methodNotFound": { 330 | "request": { 331 | "jsonrpc": "2.0", 332 | "method": "getPlayer", 333 | "params": [], 334 | "id": 1001 335 | }, 336 | "response": { 337 | "jsonrpc": "2.0", 338 | "id": 1001, 339 | "error": { 340 | "code": -32601, 341 | "message": "Method not found" 342 | } 343 | } 344 | }, 345 | "errorWithDataAttribute": { 346 | "request": { 347 | "jsonrpc": "2.0", 348 | "method": "getErrorWithData", 349 | "params": [], 350 | "id": 1002 351 | }, 352 | "response": { 353 | "jsonrpc": "2.0", 354 | "id": 1002, 355 | "error": { 356 | "code": -32000, 357 | "message": "This is an error with data attribute", 358 | "data": "Error data" 359 | } 360 | } 361 | }, 362 | "errorWithNullDataAttribute": { 363 | "request": { 364 | "jsonrpc": "2.0", 365 | "method": "getErrorWithNullData", 366 | "params": [], 367 | "id": 1003 368 | }, 369 | "response": { 370 | "jsonrpc": "2.0", 371 | "id": 1003, 372 | "error": { 373 | "code": -32000, 374 | "message": "This is an error with null data attribute", 375 | "data": null 376 | } 377 | } 378 | }, 379 | "errorWithStructuredDataAttribute": { 380 | "request": { 381 | "jsonrpc": "2.0", 382 | "method": "getErrorWithStructuredData", 383 | "params": [], 384 | "id": 1003 385 | }, 386 | "response": { 387 | "jsonrpc": "2.0", 388 | "id": 1003, 389 | "error": { 390 | "code": -32000, 391 | "message": "This is an error with structured data attribute", 392 | "data": { 393 | "data_status": 11, 394 | "data_message": "Message" 395 | } 396 | } 397 | } 398 | }, 399 | "logout": { 400 | "request": { 401 | "jsonrpc": "2.0", 402 | "method": "logout", 403 | "params": { 404 | "token": "fgt612" 405 | }, 406 | "id": 29314 407 | }, 408 | "response": { 409 | "jsonrpc": "2.0", 410 | "id": 29314, 411 | "result": null 412 | } 413 | } 414 | } -------------------------------------------------------------------------------- /core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple-json-rpc 5 | com.github.arteam 6 | 1.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | simple-json-rpc-core 11 | Core classes of simple-json-rpc 12 | 13 | simple-json-rpc-core 14 | 15 | 16 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcError.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Date: 7/31/14 10 | * Time: 6:03 PM 11 | * Annotation for marking an exception as a JSON-RPC error 12 | */ 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface JsonRpcError { 16 | 17 | /** 18 | * JSON-RPC error code 19 | * 20 | * @return error code 21 | */ 22 | int code() default 0; 23 | 24 | /** 25 | * JSON-RPC error message. 26 | * If empty then the exception message will be used 27 | * 28 | * @return error message 29 | */ 30 | String message() default ""; 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcErrorData.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotation for marking a property of an exception to use as a JSON-RPC error's data property. 10 | */ 11 | @Target({ElementType.FIELD, ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface JsonRpcErrorData { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcMethod.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Date: 07.06.14 10 | * Time: 13:02 11 | *

Annotation for marking a method as eligible for calling from the web. 12 | * Makes sense only for public non-static methods.

13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface JsonRpcMethod { 17 | 18 | /** 19 | * Method RPC name. By default, the actual method name is used. 20 | * 21 | * @return method RPC name 22 | */ 23 | String value() default ""; 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcOptional.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Date: 6/15/14 10 | * Time: 1:49 AM 11 | *

Annotation for marking a parameter as an optional.

12 | *

It means a client isn't forced to pass this parameter to the method. If the client doesn't provide it, 13 | * {@code null} value is used for complex types and an appropriate default value for primitives.

14 | */ 15 | @Target(ElementType.PARAMETER) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface JsonRpcOptional { 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcParam.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Date: 07.06.14 10 | * Time: 13:02 11 | * Annotation for marking RPC method parameter. 12 | *

Because Java doesn't retain information about method names in a class file and 13 | * therefore this information is not available in runtime, this annotation MUST 14 | * be placed on all the method parameters.

15 | * 16 | *

Otherwise {@link IllegalArgumentException} will be generated in runtime and 17 | * an error message will be returned to a client.

18 | */ 19 | @Target(ElementType.PARAMETER) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | public @interface JsonRpcParam { 22 | 23 | /** 24 | * RPC method parameter name. MUST be specified. 25 | * 26 | * @return parameter name 27 | */ 28 | String value(); 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/annotation/JsonRpcService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Inherited; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Date: 8/2/14 11 | * Time: 6:10 PM 12 | * Annotation for marking a service as a JSON-RPC service 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.TYPE) 16 | @Inherited 17 | public @interface JsonRpcService { 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/domain/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Date: 07.06.14 10 | * Time: 15:16 11 | *

Representation of a JSON-RPC error message

12 | */ 13 | public record ErrorMessage(@JsonProperty("code") int code, @JsonProperty("message") String message, 14 | @JsonProperty("data") @JsonInclude(JsonInclude.Include.NON_NULL) @Nullable JsonNode data) { 15 | 16 | public ErrorMessage(@JsonProperty("code") int code, 17 | @JsonProperty("message") String message, 18 | @JsonProperty("data") @Nullable JsonNode data) { 19 | this.code = code; 20 | this.message = message; 21 | this.data = data; 22 | } 23 | 24 | public int getCode() { 25 | return code; 26 | } 27 | 28 | public String getMessage() { 29 | return message; 30 | } 31 | 32 | @Nullable 33 | public JsonNode getData() { 34 | return data; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "ErrorMessage{code=" + code + ", message=" + message + ", data=" + data + "}"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/domain/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.domain; 2 | 3 | import com.fasterxml.jackson.databind.node.NullNode; 4 | import com.fasterxml.jackson.databind.node.ValueNode; 5 | 6 | /** 7 | * Date: 07.06.14 8 | * Time: 12:35 9 | *

Representation of a JSON-RPC error response

10 | */ 11 | public record ErrorResponse(ValueNode id, 12 | ErrorMessage error, 13 | String jsonrpc) implements Response { 14 | 15 | public static final String VERSION = "2.0"; 16 | 17 | public static ErrorResponse of(ErrorMessage error) { 18 | return new ErrorResponse(NullNode.getInstance(), error, VERSION); 19 | } 20 | 21 | public static ErrorResponse of(ValueNode id, ErrorMessage error) { 22 | return new ErrorResponse(id, error, VERSION); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/domain/Request.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.domain; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.NullNode; 5 | import com.fasterxml.jackson.databind.node.ValueNode; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | /** 9 | * Date: 07.06.14 10 | * Time: 12:24 11 | *

Representation of a JSON-RPC request

12 | */ 13 | public record Request(@Nullable String jsonrpc, @Nullable String method, 14 | @Nullable JsonNode params, 15 | @Nullable ValueNode id) { 16 | 17 | public ValueNode id() { 18 | return id != null ? id : NullNode.getInstance(); 19 | } 20 | 21 | public JsonNode params() { 22 | return params != null ? params : NullNode.getInstance(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/domain/Response.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.domain; 2 | 3 | import com.fasterxml.jackson.databind.node.ValueNode; 4 | 5 | /** 6 | * Date: 07.06.14 7 | * Time: 12:34 8 | *

Base representation of a JSON-RPC response (success or error)

9 | */ 10 | public interface Response { 11 | 12 | String jsonrpc(); 13 | 14 | ValueNode id(); 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/arteam/simplejsonrpc/core/domain/SuccessResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.core.domain; 2 | 3 | import com.fasterxml.jackson.databind.node.ValueNode; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | /** 7 | * Date: 07.06.14 8 | * Time: 12:31 9 | * Representation of a successful JSON-RPC response 10 | */ 11 | public record SuccessResponse(ValueNode id, 12 | @Nullable Object result, 13 | String jsonrpc) implements Response { 14 | public static final String VERSION = "2.0"; 15 | } 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.arteam 7 | simple-json-rpc 8 | 1.4-SNAPSHOT 9 | pom 10 | 11 | simple-json-rpc 12 | Base module for simple-json-rpc modules 13 | 14 | 15 | core 16 | client 17 | server 18 | 19 | 20 | 21 | UTF-8 22 | 23 | 2.19.0 24 | 26.0.2 25 | 2.0.17 26 | 27 | 28 | 5.13.0 29 | 3.27.3 30 | 31 | 32 | 33 | scm:git:git@github.com:arteam/simple-json-rpc.git 34 | https://github.com/arteam/simple-json-rpc 35 | HEAD 36 | 37 | 38 | https://github.com/arteam/simple-json-rpc 39 | 40 | 41 | 42 | arteam 43 | Artem Prigoda 44 | prigoda.artem@ya.ru 45 | 46 | 47 | 48 | 49 | 50 | MIT License 51 | http://opensource.org/licenses/MIT 52 | repo 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 3.14.0 63 | 64 | 17 65 | 17 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-surefire-plugin 71 | 3.5.3 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-release-plugin 80 | 3.1.1 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-source-plugin 86 | 3.3.1 87 | 88 | 89 | attach-sources 90 | 91 | jar 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-javadoc-plugin 100 | 3.11.2 101 | 102 | false 103 | false 104 | false 105 | 106 | 107 | 108 | attach-javadocs 109 | 110 | jar 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | release-sign-artifacts 121 | 122 | 123 | performRelease 124 | true 125 | 126 | 127 | 128 | 129 | 130 | org.apache.maven.plugins 131 | maven-gpg-plugin 132 | 3.2.7 133 | 134 | 135 | --no-tty 136 | 137 | 138 | 139 | 140 | sign-artifacts 141 | verify 142 | 143 | sign 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | com.fasterxml.jackson 157 | jackson-bom 158 | ${jackson.version} 159 | import 160 | pom 161 | 162 | 163 | org.junit 164 | junit-bom 165 | ${junit.version} 166 | pom 167 | import 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | com.fasterxml.jackson.core 176 | jackson-databind 177 | 178 | 179 | 180 | org.jetbrains 181 | annotations 182 | ${intellij.annotations.version} 183 | 184 | 185 | 186 | org.junit.jupiter 187 | junit-jupiter-api 188 | test 189 | 190 | 191 | 192 | org.junit.jupiter 193 | junit-jupiter-engine 194 | test 195 | 196 | 197 | 198 | org.assertj 199 | assertj-core 200 | ${assertj.version} 201 | test 202 | 203 | 204 | 205 | com.fasterxml.jackson.datatype 206 | jackson-datatype-jdk8 207 | test 208 | 209 | 210 | 211 | 212 | 213 | 214 | sonatype-nexus-staging 215 | Nexus Release Repository 216 | https://oss.sonatype.org/service/local/staging/deploy/maven2 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | ## JSON-RPC 2.0 server 2 | 3 | If you want to expose your service via the JSON-RPC protocol, you need to model it as a JSON-RPC service. For this you 4 | can use the `@JsonRpcService`, `@JsonRpcMethod` and `@JsonRpcParam` annotations. 5 | 6 | * `@JsonRpcService` marks a class as a JSON-RPC service. 7 | * `@JsonRpcMethod` marks a method as eligible for calling from the network. 8 | * `@JsonRpcParam` is a mandatory annotation for a method parameter and should contain the parameter name (this is a 9 | requirement, because Java compiler doesn't retain information about parameter names in a class file and therefore this 10 | information is not available in the runtime). 11 | 12 | Additional annotations: 13 | 14 | * `@JsonRpcOptional` is used for marking a method parameter as an optional, so the caller is able to ignore it when 15 | invokes the method. 16 | * `@JsonRpcError` is used for marking an exception as a JSON-RPC error. 17 | * `@JsonRpcErrorData` is used for marking an exception property as the `data` attribute of a JSON-RPC error. 18 | 19 | ```java 20 | 21 | @JsonRpcService 22 | public class TeamService { 23 | 24 | private final List players = Lists.newArrayList(); 25 | 26 | @JsonRpcMethod 27 | public boolean add(@JsonRpcParam("player") Player s) throws TeamServiceException { 28 | if (players.size() > 5) throw new TeamServiceException(); 29 | return players.add(s); 30 | } 31 | 32 | @JsonRpcMethod("find_by_birth_year") 33 | public List findByBirthYear(@JsonRpcParam("birth_year") final int birthYear) { 34 | return ImmutableList.copyOf(Iterables.filter(players, new Predicate() { 35 | @Override 36 | public boolean apply(Player player) { 37 | int year = new DateTime(player.getBirthDate()).getYear(); 38 | return year == birthYear; 39 | } 40 | })); 41 | } 42 | 43 | @JsonRpcMethod 44 | public Player findByInitials(@JsonRpcParam("firstName") final String firstName, 45 | @JsonRpcParam("lastName") final String lastName) { 46 | return Iterables.tryFind(players, new Predicate() { 47 | @Override 48 | public boolean apply(Player player) { 49 | return player.getFirstName().equals(firstName) && 50 | player.getLastName().equals(lastName); 51 | } 52 | }).orNull(); 53 | } 54 | 55 | @JsonRpcMethod 56 | public List find(@JsonRpcOptional @JsonRpcParam("position") final Position position, 57 | @JsonRpcOptional @JsonRpcParam("number") final int number) { 58 | return Lists.newArrayList(Iterables.filter(players, new Predicate() { 59 | @Override 60 | public boolean apply(Player player) { 61 | if (position != null && !player.getPosition().equals(position)) 62 | return false; 63 | if (number != 0 && player.getNumber() != number) 64 | return false; 65 | return true; 66 | } 67 | })); 68 | } 69 | 70 | @JsonRpcMethod 71 | public List getPlayers() { 72 | return players; 73 | } 74 | } 75 | 76 | @JsonRpcError(code = -32032, message = "It's not permitted to add new players") 77 | public class TeamServiceException extends Exception { 78 | } 79 | ``` 80 | 81 | After you modeled your service, yon can safely publish it via the *JsonRpcServer* class. 82 | 83 | ```java 84 | TeamService teamService = new TeamService(); 85 | JsonRpcServer rpcServer = new JsonRpcServer(); 86 | String textRequest = "{\n" + 87 | " \"jsonrpc\": \"2.0\",\n" + 88 | " \"method\": \"findByInitials\",\n" + 89 | " \"params\": {\n" + 90 | " \"firstName\": \"Kevin\",\n" + 91 | " \"lastName\": \"Shattenkirk\"\n" + 92 | " },\n" + 93 | " \"id\": \"92739\"\n" + 94 | "}"; 95 | String response = rpcServer.handle(textRequest, teamService); 96 | ``` 97 | 98 | See the full 99 | service [code](https://github.com/arteam/simple-json-rpc/blob/master/server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/service/TeamService.java) 100 | and more examples 101 | in [tests](https://github.com/arteam/simple-json-rpc/blob/master/server/src/test/java/com/github/arteam/simplejsonrpc/server/simple) 102 | . 103 | 104 | ## Setup 105 | 106 | Maven: 107 | 108 | ```xml 109 | 110 | com.github.arteam 111 | simple-json-rpc-server 112 | 1.2 113 | 114 | ``` 115 | 116 | ## Requirements 117 | 118 | JDK 1.8 and higher 119 | 120 | ## Dependencies 121 | 122 | * [Jackson](https://github.com/FasterXML/jackson) 2.11.0 123 | * [Guava](http://code.google.com/p/guava-libraries/) 29.0 124 | * [SLF4J](http://www.slf4j.org/) 1.7.30 125 | * [IntelliJ IDEA Annotations](http://mvnrepository.com/artifact/com.intellij/annotations/12.0) 12.0 126 | -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | simple-json-rpc 5 | com.github.arteam 6 | 1.4-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | simple-json-rpc-server 11 | Server implementation of JSON-RPC 2.0 12 | simple-json-rpc-server 13 | 14 | 15 | 16 | 17 | ${project.basedir}/src/test/resources 18 | 19 | 20 | 21 | 22 | 23 | 24 | com.github.arteam 25 | simple-json-rpc-core 26 | ${project.version} 27 | 28 | 29 | 30 | 31 | org.slf4j 32 | slf4j-api 33 | ${slf4j.version} 34 | 35 | 36 | 37 | org.slf4j 38 | slf4j-simple 39 | ${slf4j.version} 40 | test 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /server/src/main/java/com/github/arteam/simplejsonrpc/server/Reflections.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 5 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional; 6 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 7 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 8 | import com.github.arteam.simplejsonrpc.server.metadata.ClassMetadata; 9 | import com.github.arteam.simplejsonrpc.server.metadata.ErrorDataResolver; 10 | import com.github.arteam.simplejsonrpc.server.metadata.MethodMetadata; 11 | import com.github.arteam.simplejsonrpc.server.metadata.ParameterMetadata; 12 | import org.jetbrains.annotations.Nullable; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.invoke.MethodHandle; 18 | import java.lang.invoke.MethodHandles; 19 | import java.lang.invoke.VarHandle; 20 | import java.lang.reflect.Field; 21 | import java.lang.reflect.Method; 22 | import java.lang.reflect.Modifier; 23 | import java.lang.reflect.Type; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | 28 | /** 29 | * Date: 07.06.14 30 | * Time: 14:42 31 | *

Reflection utils for scanning service metadata

32 | */ 33 | class Reflections { 34 | 35 | private static final Logger log = LoggerFactory.getLogger(JsonRpcServer.class); 36 | private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 37 | 38 | private Reflections() { 39 | } 40 | 41 | /** 42 | * Finds an entity annotation with appropriate type. 43 | * 44 | * @param annotations entity annotations 45 | * @param clazz annotation class type 46 | * @param actual compile-time annotation type 47 | * @return appropriate annotation or {@code null} if it wasn't found 48 | */ 49 | @Nullable 50 | @SuppressWarnings("unchecked") 51 | public static T getAnnotation(@Nullable Annotation[] annotations, Class clazz) { 52 | if (annotations != null) { 53 | for (Annotation annotation : annotations) { 54 | if (annotation != null && annotation.annotationType().equals(clazz)) { 55 | return (T) annotation; 56 | } 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | /** 63 | * Gets class metadata for JSON-RPC processing. 64 | * It scans the class and builds JSON-RPC meta-information about methods and it's parameters 65 | * 66 | * @param clazz actual service class 67 | * @return service class JSON-RPC meta-information 68 | */ 69 | public static ClassMetadata getClassMetadata(Class clazz) { 70 | Map methodsMetadata = new HashMap<>(); 71 | Class searchType = clazz; 72 | // Search through the class hierarchy 73 | while (searchType != null) { 74 | for (Method method : searchType.getDeclaredMethods()) { 75 | String methodName = method.getName(); 76 | // Checks the annotation 77 | JsonRpcMethod jsonRpcMethod = getAnnotation(method.getDeclaredAnnotations(), JsonRpcMethod.class); 78 | if (jsonRpcMethod == null) { 79 | continue; 80 | } 81 | 82 | // Check modifiers, only public non-static methods are permitted 83 | int modifiers = method.getModifiers(); 84 | if (!Modifier.isPublic(modifiers)) { 85 | log.warn("Method '" + methodName + "' is not public"); 86 | continue; 87 | } 88 | if (Modifier.isStatic(modifiers)) { 89 | log.warn("Method '" + methodName + "' is static"); 90 | continue; 91 | } 92 | 93 | String rpcMethodName = !jsonRpcMethod.value().isEmpty() ? jsonRpcMethod.value() : methodName; 94 | Map methodParams = getMethodParameters(method); 95 | if (methodParams == null) { 96 | log.warn("Method '" + methodName + "' has misconfigured parameters"); 97 | continue; 98 | } 99 | 100 | MethodHandle methodHandle; 101 | try { 102 | methodHandle = MethodHandles.privateLookupIn(method.getDeclaringClass(), LOOKUP) 103 | .unreflect(method); 104 | } catch (IllegalAccessException e) { 105 | throw new RuntimeException(e); 106 | } 107 | MethodMetadata oldMethodMetadata = methodsMetadata.put(rpcMethodName, 108 | new MethodMetadata(rpcMethodName, methodHandle, methodParams)); 109 | if (oldMethodMetadata != null) { 110 | // Throw exception, because two methods with the same name leads to unexpected behaviour 111 | throw new IllegalArgumentException("There two methods with the same name in " + clazz); 112 | } 113 | } 114 | searchType = searchType.getSuperclass(); 115 | } 116 | 117 | boolean isService = getAnnotation(clazz != null ? clazz.getAnnotations() : null, JsonRpcService.class) != null; 118 | return new ClassMetadata(isService, Map.copyOf(methodsMetadata)); 119 | } 120 | 121 | /** 122 | * Gets JSON-RPC meta-information about method parameters. 123 | * 124 | * @param method actual method 125 | * @return map of parameters metadata by their names 126 | */ 127 | @Nullable 128 | private static Map getMethodParameters(Method method) { 129 | Annotation[][] allParametersAnnotations = method.getParameterAnnotations(); 130 | int methodParamsSize = allParametersAnnotations.length; 131 | Class[] parameterTypes = method.getParameterTypes(); 132 | Type[] genericParameterTypes = method.getGenericParameterTypes(); 133 | 134 | Map parametersMetadata = new HashMap<>(); 135 | for (int i = 0; i < methodParamsSize; i++) { 136 | Annotation[] parameterAnnotations = allParametersAnnotations[i]; 137 | JsonRpcParam jsonRpcParam = Reflections.getAnnotation(parameterAnnotations, JsonRpcParam.class); 138 | if (jsonRpcParam == null) { 139 | log.warn("Annotation @JsonRpcParam is not set for the " + i + 140 | " parameter of a method '" + method.getName() + "'"); 141 | return null; 142 | } 143 | 144 | String paramName = jsonRpcParam.value(); 145 | boolean optional = Reflections.getAnnotation(parameterAnnotations, JsonRpcOptional.class) != null; 146 | ParameterMetadata oldParameterMetadata = parametersMetadata.put(paramName, 147 | new ParameterMetadata(paramName, parameterTypes[i], genericParameterTypes[i], i, optional)); 148 | if (oldParameterMetadata != null) { 149 | log.error("There two parameters with the same name in method '" + method.getName() + 150 | "' of the class '" + method.getDeclaringClass() + "'"); 151 | return null; 152 | } 153 | } 154 | return Map.copyOf(parametersMetadata); 155 | } 156 | 157 | static ErrorDataResolver buildErrorDataResolver(Class throwableClass) { 158 | Class c = throwableClass; 159 | VarHandle dataField = null; 160 | MethodHandle dataMethod = null; 161 | while (c != null) { 162 | for (Field field : c.getDeclaredFields()) { 163 | if (field.isAnnotationPresent(JsonRpcErrorData.class)) { 164 | if (dataField != null) { 165 | throw new IllegalArgumentException("Ambiguous configuration: there is more than one " + 166 | "@JsonRpcErrorData annotated property in " + c.getName()); 167 | } 168 | try { 169 | dataField = MethodHandles.privateLookupIn(field.getDeclaringClass(), LOOKUP) 170 | .unreflectVarHandle(field); 171 | } catch (IllegalAccessException e) { 172 | throw new IllegalStateException(e); 173 | } 174 | } 175 | } 176 | for (Method method : c.getDeclaredMethods()) { 177 | if (method.isAnnotationPresent(JsonRpcErrorData.class)) { 178 | if (method.getReturnType() == void.class) { 179 | log.warn("Method '{}' annotated with 'JsonRpcErrorData' cannot have void return type", method.getName()); 180 | continue; 181 | } 182 | if (method.getParameterCount() > 0) { 183 | log.warn("Method '{}' annotated with 'JsonRpcErrorData' must be with zero arguments", method.getName()); 184 | continue; 185 | } 186 | if (dataField != null || dataMethod != null) { 187 | throw new IllegalArgumentException("Ambiguous configuration: there is more than one " + 188 | "@JsonRpcErrorData annotated property in " + c.getName()); 189 | } 190 | try { 191 | dataMethod = MethodHandles.privateLookupIn(method.getDeclaringClass(), LOOKUP) 192 | .unreflect(method); 193 | } catch (IllegalAccessException e) { 194 | throw new RuntimeException(e); 195 | } 196 | } 197 | } 198 | c = c.getSuperclass(); 199 | } 200 | if (dataField != null) { 201 | return new ThrowableErrorResolver(dataField::get); 202 | } else if (dataMethod != null) { 203 | return new ThrowableErrorResolver(dataMethod::invoke); 204 | } else { 205 | return t -> Optional.empty(); 206 | } 207 | } 208 | 209 | @FunctionalInterface 210 | private interface DataFunction { 211 | Object get(Throwable t) throws Throwable; 212 | } 213 | 214 | private record ThrowableErrorResolver(DataFunction function) implements ErrorDataResolver { 215 | 216 | @Override 217 | public Optional resolveData(Throwable throwable) throws Exception { 218 | try { 219 | return Optional.ofNullable(function.get(throwable)); 220 | } catch (Throwable e) { 221 | throw new IllegalStateException(e); 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /server/src/main/java/com/github/arteam/simplejsonrpc/server/metadata/ClassMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.metadata; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Date: 8/1/14 7 | * Time: 7:42 PM 8 | *

9 | * Metadata about a Java class 10 | */ 11 | public record ClassMetadata(boolean service, 12 | Map methods) { 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/java/com/github/arteam/simplejsonrpc/server/metadata/ErrorDataResolver.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.metadata; 2 | 3 | import java.util.Optional; 4 | 5 | @FunctionalInterface 6 | public interface ErrorDataResolver { 7 | 8 | Optional resolveData(Throwable throwable) throws Exception; 9 | } 10 | -------------------------------------------------------------------------------- /server/src/main/java/com/github/arteam/simplejsonrpc/server/metadata/MethodMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.metadata; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.util.Map; 5 | 6 | /** 7 | * Date: 8/1/14 8 | * Time: 7:42 PM 9 | *

10 | * Metadata about a Java method 11 | */ 12 | public record MethodMetadata(String name, MethodHandle methodHandle, 13 | Map params) { 14 | } 15 | -------------------------------------------------------------------------------- /server/src/main/java/com/github/arteam/simplejsonrpc/server/metadata/ParameterMetadata.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.metadata; 2 | 3 | import java.lang.reflect.Type; 4 | 5 | /** 6 | * Date: 8/1/14 7 | * Time: 7:44 PM 8 | *

9 | * Method parameter metadata 10 | */ 11 | public record ParameterMetadata(String name, Class type, Type genericType, 12 | int index, boolean optional) { 13 | } 14 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/JsonRpcServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.SerializationFeature; 6 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 7 | import com.github.arteam.simplejsonrpc.server.JsonRpcServer; 8 | import com.github.arteam.simplejsonrpc.server.simple.service.TeamService; 9 | import com.github.arteam.simplejsonrpc.server.simple.util.RequestResponse; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.util.Map; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | 18 | /** 19 | * Date: 7/28/14 20 | * Time: 10:29 PM 21 | * Tests typical patterns of a JSON-RPC interaction 22 | */ 23 | public class JsonRpcServiceTest { 24 | 25 | private static final ObjectMapper userMapper = new ObjectMapper() 26 | .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) 27 | .registerModule(new Jdk8Module()); 28 | private static final JsonRpcServer rpcServer = new JsonRpcServer(userMapper); 29 | private static final TeamService teamService = new TeamService(); 30 | 31 | private static Map testData; 32 | 33 | @BeforeAll 34 | public static void init() throws Exception { 35 | try (var is = requireNonNull(JsonRpcServiceTest.class.getResourceAsStream("/test_data.json"))) { 36 | testData = userMapper.readValue(is.readAllBytes(), new TypeReference<>() { 37 | }); 38 | } 39 | } 40 | 41 | /** 42 | * Tests adding of a complex object 43 | */ 44 | @Test 45 | public void testAddPlayer() { 46 | test("add_player"); 47 | test("find_shattenkirk"); 48 | } 49 | 50 | /** 51 | * Tests usual case (request and complex response) 52 | */ 53 | @Test 54 | public void testFindPlayer() { 55 | test("find_player"); 56 | } 57 | 58 | /** 59 | * Tests null as a result 60 | */ 61 | @Test 62 | public void testPlayerIsNotFound() { 63 | test("player_is_not_found"); 64 | } 65 | 66 | /** 67 | * Tests params are set as array 68 | */ 69 | @Test 70 | public void testFindPlayerWithArrayParams() { 71 | test("find_player_array"); 72 | } 73 | 74 | /** 75 | * Tests optional fields 76 | */ 77 | @Test 78 | public void testFind() { 79 | test("find"); 80 | } 81 | 82 | /** 83 | * Tests overridden method name and response as a list of objects 84 | */ 85 | @Test 86 | public void testFindByBirthYear() { 87 | test("findByBirthYear"); 88 | } 89 | 90 | /** 91 | * Tests optional fields in array params 92 | */ 93 | @Test 94 | public void testFindWithArrayNullParams() { 95 | test("find_array_null_params"); 96 | } 97 | 98 | /** 99 | * Tests calling a method from super-class and method without parameters 100 | */ 101 | @Test 102 | public void testIsAlive() { 103 | test("isAlive"); 104 | } 105 | 106 | /** 107 | * Tests a notification request 108 | */ 109 | @Test 110 | public void testNotification() { 111 | test("notification"); 112 | } 113 | 114 | /** 115 | * Tests a batch request 116 | */ 117 | @Test 118 | public void testBatch() { 119 | test("batch"); 120 | } 121 | 122 | /** 123 | * Tests a mixed request 124 | */ 125 | @Test 126 | public void testBatchWithNotification() { 127 | test("batchWithNotification"); 128 | } 129 | 130 | /** 131 | * Tests passing list as a parameter 132 | */ 133 | @Test 134 | public void testFindPlayersByNames() { 135 | test("findPlayersByFirstNames"); 136 | } 137 | 138 | @Test 139 | public void testFindPlayersByNumbers() { 140 | test("findPlayersByNumbers"); 141 | } 142 | 143 | @Test 144 | public void testGetContractSums() { 145 | test("getContractSums"); 146 | } 147 | 148 | @Test 149 | public void testGenericFindPlayersByNumbers() { 150 | test("genericFindPlayersByNumbers"); 151 | } 152 | 153 | private void test(String testName) { 154 | try { 155 | RequestResponse requestResponse = testData.get(testName); 156 | String textRequest = userMapper.writeValueAsString(requestResponse.request()); 157 | 158 | String actual = rpcServer.handle(textRequest, teamService); 159 | if (!actual.isEmpty()) { 160 | assertThat(userMapper.readTree(actual)).isEqualTo(requestResponse.response()); 161 | } else { 162 | assertThat(actual).isEqualTo(requestResponse.response().asText()); 163 | } 164 | } catch (Exception e) { 165 | throw new IllegalStateException(e); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/domain/Player.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.domain; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * Date: 7/29/14 7 | * Time: 12:49 PM 8 | */ 9 | public record Player(String firstName, String lastName, 10 | Team team, int number, 11 | Position position, Date birthDate, 12 | double capHit) { 13 | } 14 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/domain/Position.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | 6 | /** 7 | * Date: 7/29/14 8 | * Time: 12:51 PM 9 | */ 10 | public enum Position { 11 | GOALTENDER("G"), DEFENDER("D"), RIGHT_WINGER("RW"), LEFT_WINGER("LW"), CENTER("C"); 12 | 13 | private static final Position[] VALUES = values(); 14 | 15 | private final String code; 16 | 17 | Position(String code) { 18 | this.code = code; 19 | } 20 | 21 | @JsonValue 22 | public String getCode() { 23 | return code; 24 | } 25 | 26 | @JsonCreator 27 | public static Position byCode(String code) { 28 | for (Position position : VALUES) { 29 | if (position.code.equalsIgnoreCase(code)) { 30 | return position; 31 | } 32 | } 33 | throw new IllegalStateException("Unable find Position by code=" + code); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/domain/Team.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.domain; 2 | 3 | /** 4 | * Date: 7/29/14 5 | * Time: 2:07 PM 6 | */ 7 | public record Team(String name, String league) { 8 | } 9 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/EmptyMessageTeamServiceException.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | 5 | /** 6 | * Date: 7/31/14 7 | * Time: 6:23 PM 8 | */ 9 | @JsonRpcError(code = -32000) 10 | public class EmptyMessageTeamServiceException extends RuntimeException { 11 | 12 | public EmptyMessageTeamServiceException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithDataField.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30000, message = "Error with data (field)") 7 | public class ExceptionWithDataField extends RuntimeException { 8 | 9 | @JsonRpcErrorData 10 | private final String[] data; 11 | 12 | public ExceptionWithDataField(String message, String[] data) { 13 | super(message); 14 | this.data = data; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithDataGetter.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30001, message = "Error with data (getter)") 7 | public class ExceptionWithDataGetter extends RuntimeException { 8 | 9 | private final String[] data; 10 | 11 | public ExceptionWithDataGetter(String message, String[] data) { 12 | super(message); 13 | this.data = data; 14 | } 15 | 16 | @JsonRpcErrorData 17 | public String[] getData() { 18 | return data; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithDataMultipleFields.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30002, message = "Error with data (multiple fields)") 7 | public class ExceptionWithDataMultipleFields extends RuntimeException { 8 | 9 | @JsonRpcErrorData 10 | private final String[] data; 11 | @JsonRpcErrorData 12 | private final String anotherData; 13 | 14 | public ExceptionWithDataMultipleFields(String message, String[] data, String anotherData) { 15 | super(message); 16 | this.data = data; 17 | this.anotherData = anotherData; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithDataMultipleGetters.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30003, message = "Error with data (multiple getters)") 7 | public class ExceptionWithDataMultipleGetters extends RuntimeException { 8 | 9 | private final String[] data; 10 | private final String anotherData; 11 | 12 | public ExceptionWithDataMultipleGetters(String message, String[] data, String anotherData) { 13 | super(message); 14 | this.data = data; 15 | this.anotherData = anotherData; 16 | } 17 | 18 | @JsonRpcErrorData 19 | public String[] getData() { 20 | return data; 21 | } 22 | 23 | @JsonRpcErrorData 24 | public String getAnotherData() { 25 | return anotherData; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithDataMultipleMixed.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30004, message = "Error with data (multiple getters)") 7 | public class ExceptionWithDataMultipleMixed extends RuntimeException { 8 | 9 | @JsonRpcErrorData 10 | private final String[] data; 11 | 12 | public ExceptionWithDataMultipleMixed(String message, String[] data) { 13 | super(message); 14 | this.data = data; 15 | } 16 | 17 | @JsonRpcErrorData 18 | public String[] getData() { 19 | return data; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/ExceptionWithWrongMethods.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcErrorData; 5 | 6 | @JsonRpcError(code = -30005, message = "Error with wrong methods") 7 | public class ExceptionWithWrongMethods extends RuntimeException { 8 | 9 | private final String[] data; 10 | 11 | public ExceptionWithWrongMethods(String message, String[] data) { 12 | super(message); 13 | this.data = data; 14 | } 15 | 16 | @JsonRpcErrorData 17 | public void returnVoid() { 18 | } 19 | 20 | @JsonRpcErrorData 21 | public String hasArgs(String s) { 22 | return s; 23 | } 24 | 25 | @JsonRpcErrorData 26 | public String[] getData() { 27 | return data; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/exception/TeamServiceAuthException.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.exception; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcError; 4 | 5 | /** 6 | * Date: 7/31/14 7 | * Time: 6:23 PM 8 | */ 9 | @JsonRpcError(code = -32032, message = "You are not authorized to the team service") 10 | public class TeamServiceAuthException extends RuntimeException { 11 | 12 | public TeamServiceAuthException(String message) { 13 | super(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/service/BaseService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.service; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 4 | 5 | /** 6 | * Date: 7/30/14 7 | * Time: 3:29 PM 8 | */ 9 | public class BaseService { 10 | 11 | @JsonRpcMethod 12 | public boolean isAlive() { 13 | return true; 14 | } 15 | 16 | @JsonRpcMethod 17 | public void updateCache() { 18 | new Thread(() -> { 19 | System.out.println("Updating cache..."); 20 | // Some time... 21 | System.out.println("Done!"); 22 | }).start(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/service/BogusService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.service; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 5 | 6 | /** 7 | * Date: 8/2/14 8 | * Time: 6:25 PM 9 | */ 10 | @JsonRpcService 11 | public class BogusService { 12 | 13 | @JsonRpcMethod 14 | public void bogus() { 15 | System.out.println("Bogus"); 16 | } 17 | 18 | @JsonRpcMethod("bogus") 19 | public void bogus2() { 20 | System.out.println("Bogus2"); 21 | } 22 | 23 | @JsonRpcMethod 24 | public void notBogus() { 25 | System.out.println("Not bogus"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/service/TeamService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.service; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcOptional; 5 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 6 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 7 | import com.github.arteam.simplejsonrpc.server.simple.domain.Player; 8 | import com.github.arteam.simplejsonrpc.server.simple.domain.Position; 9 | import com.github.arteam.simplejsonrpc.server.simple.domain.Team; 10 | import com.github.arteam.simplejsonrpc.server.simple.exception.EmptyMessageTeamServiceException; 11 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithDataField; 12 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithDataGetter; 13 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithDataMultipleFields; 14 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithDataMultipleGetters; 15 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithDataMultipleMixed; 16 | import com.github.arteam.simplejsonrpc.server.simple.exception.ExceptionWithWrongMethods; 17 | import com.github.arteam.simplejsonrpc.server.simple.exception.TeamServiceAuthException; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.time.LocalDate; 21 | import java.time.ZoneOffset; 22 | import java.util.Date; 23 | import java.util.LinkedHashMap; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Optional; 27 | import java.util.stream.Collectors; 28 | import java.util.stream.Stream; 29 | 30 | /** 31 | * Date: 7/27/14 32 | * Time: 8:46 PM 33 | */ 34 | @JsonRpcService 35 | public class TeamService extends BaseService { 36 | 37 | private final Team stLouis = new Team("St. Louis Blues", "NHL"); 38 | private final Team tampa = new Team("Tampa Bay Lightning", "NHL"); 39 | private final List players = Stream.of( 40 | new Player("David", "Backes", stLouis, 42, Position.CENTER, date("1984-05-01"), 4.5), 41 | new Player("Vladimir", "Tarasenko", stLouis, 91, Position.RIGHT_WINGER, date("1991-12-13"), 0.9), 42 | new Player("Jack", "Allen", stLouis, 34, Position.GOALTENDER, date("1990-08-07"), 0.5), 43 | new Player("Jay", "Bouwmeester", stLouis, 19, Position.DEFENDER, date("1985-08-07"), 5.4), 44 | new Player("Steven", "Stamkos", tampa, 91, Position.CENTER, date("1990-02-07"), 7.5), 45 | new Player("Ryan", "Callahan", tampa, 24, Position.RIGHT_WINGER, date("1985-03-21"), 5.8), 46 | new Player("Ben", "Bishop", tampa, 30, Position.GOALTENDER, date("1986-11-21"), 2.3), 47 | new Player("Victor", "Hedman", tampa, 77, Position.DEFENDER, date("1990-12-18"), 4.0)) 48 | .collect(Collectors.toList()); 49 | 50 | @JsonRpcMethod 51 | public boolean add(@JsonRpcParam("player") Player s) { 52 | return players.add(s); 53 | } 54 | 55 | @JsonRpcMethod("find_by_birth_year") 56 | public List findByBirthYear(@JsonRpcParam("birth_year") final int birthYear) { 57 | return players.stream().filter(player -> { 58 | int year = LocalDate.ofInstant(player.birthDate().toInstant(), ZoneOffset.UTC).getYear(); 59 | return year == birthYear; 60 | }).collect(Collectors.toList()); 61 | } 62 | 63 | @JsonRpcMethod 64 | public Player findByInitials(@JsonRpcParam("firstName") final String firstName, 65 | @JsonRpcParam("lastName") final String lastName) { 66 | return players.stream() 67 | .filter(player -> player.firstName().equals(firstName) && player.lastName().equals(lastName)) 68 | .findAny() 69 | .orElse(null); 70 | } 71 | 72 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 73 | @JsonRpcMethod 74 | public List find(@JsonRpcOptional @JsonRpcParam("position") @Nullable Position position, 75 | @JsonRpcOptional @JsonRpcParam("number") int number, 76 | @JsonRpcOptional @JsonRpcParam("team") Optional team, 77 | @JsonRpcOptional @JsonRpcParam("firstName") @Nullable String firstName, 78 | @JsonRpcOptional @JsonRpcParam("lastName") @Nullable String lastName, 79 | @JsonRpcOptional @JsonRpcParam("birthDate") @Nullable Date birthDate, 80 | @JsonRpcOptional @JsonRpcParam("capHit") Optional capHit) { 81 | return players.stream().filter(player -> { 82 | if (position != null && !player.position().equals(position)) return false; 83 | if (number != 0 && player.number() != number) return false; 84 | if (team.isPresent() && !player.team().equals(team.get())) return false; 85 | if (firstName != null && !player.firstName().equals(firstName)) return false; 86 | if (lastName != null && !player.lastName().equals(lastName)) return false; 87 | if (birthDate != null && !player.birthDate().equals(birthDate)) return false; 88 | if (capHit.isPresent() && player.capHit() != capHit.get()) return false; 89 | return true; 90 | }).collect(Collectors.toList()); 91 | } 92 | 93 | @JsonRpcMethod 94 | public List getPlayers() { 95 | return players; 96 | } 97 | 98 | public List bogusGetPlayers() { 99 | return players; 100 | } 101 | 102 | @JsonRpcMethod 103 | private List privateGetPlayers() { 104 | return players; 105 | } 106 | 107 | @JsonRpcMethod 108 | public Player bogusFindByInitials(@JsonRpcParam("firstName") String firstName, String lastName) { 109 | return findByInitials(firstName, lastName); 110 | } 111 | 112 | @JsonRpcMethod 113 | public Player findByCapHit(@JsonRpcParam("cap") double capHit) { 114 | throw new IllegalStateException("Not implemented"); 115 | } 116 | 117 | @JsonRpcMethod 118 | public long login(@JsonRpcParam("login") String login, @JsonRpcParam("password") String password) { 119 | if (!login.equals("CAFE") && !password.equals("BABE")) { 120 | throw new TeamServiceAuthException("Not authorized"); 121 | } 122 | return 0xCAFEBABE; 123 | } 124 | 125 | @JsonRpcMethod 126 | public long bogusMessageLogin(@JsonRpcParam("login") String login, @JsonRpcParam("password") String password) { 127 | if (!login.equals("CAFE") && !password.equals("BABE")) { 128 | throw new EmptyMessageTeamServiceException("Not authorized"); 129 | } 130 | return 0xCAFEBABE; 131 | } 132 | 133 | @JsonRpcMethod 134 | public long errorDataFieldLogin(@JsonRpcParam("login") String login, @JsonRpcParam("password") String password) { 135 | if (!login.equals("CAFE") && !password.equals("BABE")) { 136 | throw new ExceptionWithDataField("Detailed message", new String[]{"Data 1", "Data 2"}); 137 | } 138 | return 0xCAFEBABE; 139 | } 140 | 141 | @JsonRpcMethod 142 | public long errorDataGetterLogin(@JsonRpcParam("login") String login, @JsonRpcParam("password") String password) { 143 | if (!login.equals("CAFE") && !password.equals("BABE")) { 144 | throw new ExceptionWithDataGetter("Detailed message", new String[]{"Data 1", "Data 2"}); 145 | } 146 | return 0xCAFEBABE; 147 | } 148 | 149 | @JsonRpcMethod 150 | public long errorDataMultipleFieldsLogin(@JsonRpcParam("login") String login, 151 | @JsonRpcParam("password") String password) { 152 | if (!login.equals("CAFE") && !password.equals("BABE")) { 153 | throw new ExceptionWithDataMultipleFields( 154 | "Detailed message", 155 | new String[]{"Data 1", "Data 2"}, 156 | "AnotherData"); 157 | } 158 | return 0xCAFEBABE; 159 | } 160 | 161 | @JsonRpcMethod 162 | public long errorDataMultipleGettersLogin(@JsonRpcParam("login") String login, 163 | @JsonRpcParam("password") String password) { 164 | if (!login.equals("CAFE") && !password.equals("BABE")) { 165 | throw new ExceptionWithDataMultipleGetters( 166 | "Detailed message", 167 | new String[]{"Data 1", "Data 2"}, 168 | "AnotherData"); 169 | } 170 | return 0xCAFEBABE; 171 | } 172 | 173 | @JsonRpcMethod 174 | public long errorDataMultipleMixedLogin(@JsonRpcParam("login") String login, 175 | @JsonRpcParam("password") String password) { 176 | if (!login.equals("CAFE") && !password.equals("BABE")) { 177 | throw new ExceptionWithDataMultipleMixed( 178 | "Detailed message", 179 | new String[]{"Data 1", "Data 2"}); 180 | } 181 | return 0xCAFEBABE; 182 | } 183 | 184 | @JsonRpcMethod 185 | public long errorDataWrongMethodsLogin(@JsonRpcParam("login") String login, 186 | @JsonRpcParam("password") String password) { 187 | if (!login.equals("CAFE") && !password.equals("BABE")) { 188 | throw new ExceptionWithWrongMethods( 189 | "Detailed message", 190 | new String[]{"Data 1", "Data 2"}); 191 | } 192 | return 0xCAFEBABE; 193 | } 194 | 195 | @JsonRpcMethod 196 | public Player bogusFind(@JsonRpcParam("firstName") String firstName, 197 | @JsonRpcParam("firstName") String lastName, 198 | @JsonRpcParam("age") int age) { 199 | return null; 200 | } 201 | 202 | @JsonRpcMethod 203 | public List findPlayersByFirstNames(@JsonRpcParam("names") final List names) { 204 | return players.stream().filter(player -> names.contains(player.firstName())).collect(Collectors.toList()); 205 | } 206 | 207 | @JsonRpcMethod 208 | public List findPlayersByNumbers(@JsonRpcParam("numbers") final int... numbers) { 209 | return players.stream().filter(player -> { 210 | for (int number : numbers) { 211 | if (player.number() == number) { 212 | return true; 213 | } 214 | } 215 | return false; 216 | }).collect(Collectors.toList()); 217 | } 218 | 219 | @SafeVarargs 220 | @JsonRpcMethod 221 | public final List genericFindPlayersByNumbers(@JsonRpcParam("numbers") final T... numbers) { 222 | return players.stream().filter(player -> { 223 | for (T number : numbers) { 224 | if (String.valueOf(player.number()).equals(number.toString())) { 225 | return true; 226 | } 227 | } 228 | return false; 229 | }).collect(Collectors.toList()); 230 | } 231 | 232 | @JsonRpcMethod 233 | public Map getContractSums(@JsonRpcParam("contractLengths") Map contractLengths) { 234 | Map playerContractSums = new LinkedHashMap<>(); 235 | for (Player player : players) { 236 | playerContractSums.put(player.lastName(), 237 | player.capHit() * contractLengths.get(player.lastName()).intValue()); 238 | } 239 | return playerContractSums; 240 | } 241 | 242 | 243 | @JsonRpcMethod 244 | public static Date date(@JsonRpcParam("textDate") String textDate) { 245 | return Date.from(LocalDate.parse(textDate).atStartOfDay(ZoneOffset.UTC).toInstant()); 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/simple/util/RequestResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.simple.util; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | /** 6 | * Date: 7/28/14 7 | * Time: 11:26 PM 8 | */ 9 | public record RequestResponse(JsonNode request, JsonNode response) { 10 | } 11 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/spec/CalculatorService.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.spec; 2 | 3 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod; 4 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam; 5 | import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService; 6 | 7 | /** 8 | * Date: 7/31/14 9 | * Time: 8:42 PM 10 | */ 11 | @JsonRpcService 12 | public class CalculatorService { 13 | 14 | @JsonRpcMethod 15 | public long subtract(@JsonRpcParam("minuend") int m, @JsonRpcParam("subtrahend") int s) { 16 | return (long) m - (long) s; 17 | } 18 | 19 | @JsonRpcMethod 20 | public long sum(@JsonRpcParam("first") int first, @JsonRpcParam("second") int second, 21 | @JsonRpcParam("third") int third) { 22 | return (long) first + (long) second + (long) third; 23 | } 24 | 25 | @JsonRpcMethod 26 | public void update(@JsonRpcParam("i1") int i1, @JsonRpcParam("i2") int i2, 27 | @JsonRpcParam("i3") int i3, @JsonRpcParam("i4") int i4, 28 | @JsonRpcParam("i5") int i5) { 29 | System.out.println("Update"); 30 | } 31 | 32 | @JsonRpcMethod("get_data") 33 | public Object[] getData() { 34 | return new Object[]{"hello", 5}; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/test/java/com/github/arteam/simplejsonrpc/server/spec/SpecTest.java: -------------------------------------------------------------------------------- 1 | package com.github.arteam.simplejsonrpc.server.spec; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.github.arteam.simplejsonrpc.server.JsonRpcServer; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Properties; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * Date: 7/31/14 14 | * Time: 8:51 PM 15 | */ 16 | public class SpecTest { 17 | 18 | final CalculatorService calculatorService = new CalculatorService(); 19 | // Just test factory method 20 | final JsonRpcServer rpcServer = new JsonRpcServer(); 21 | final ObjectMapper mapper = new ObjectMapper(); 22 | 23 | private void test(String testName) { 24 | try (var is = SpecTest.class.getResourceAsStream("/spec/" + testName + ".properties")) { 25 | Properties testProps = new Properties(); 26 | testProps.load(is); 27 | String textRequest = testProps.getProperty("request"); 28 | JsonNode response = mapper.readTree(testProps.getProperty("response")); 29 | 30 | String actual = rpcServer.handle(textRequest, calculatorService); 31 | if (!actual.isEmpty()) { 32 | assertThat(mapper.readTree(actual)).isEqualTo(response); 33 | } else { 34 | assertThat(actual).isEqualTo(response.asText()); 35 | } 36 | } catch (Exception e) { 37 | throw new IllegalStateException(e); 38 | } 39 | } 40 | 41 | @Test 42 | public void test1() { 43 | test("test_1"); 44 | } 45 | 46 | @Test 47 | public void test2() { 48 | test("test_2"); 49 | } 50 | 51 | @Test 52 | public void test3() { 53 | test("test_3"); 54 | } 55 | 56 | @Test 57 | public void test4() { 58 | test("test_4"); 59 | } 60 | 61 | @Test 62 | public void test5() { 63 | test("test_5"); 64 | } 65 | 66 | @Test 67 | public void test6() { 68 | test("test_6"); 69 | } 70 | 71 | @Test 72 | public void test7() { 73 | test("test_7"); 74 | } 75 | 76 | @Test 77 | public void test8() { 78 | test("test_8"); 79 | } 80 | 81 | @Test 82 | public void test9() { 83 | test("test_9"); 84 | } 85 | 86 | @Test 87 | public void test10() { 88 | test("test_10"); 89 | } 90 | 91 | @Test 92 | public void test11() { 93 | test("test_11"); 94 | } 95 | 96 | @Test 97 | public void test12() { 98 | test("test_12"); 99 | } 100 | 101 | @Test 102 | public void test13() { 103 | test("test_13"); 104 | } 105 | 106 | @Test 107 | public void test14() { 108 | test("test_14"); 109 | } 110 | 111 | @Test 112 | public void test15() { 113 | test("test_15"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_array_generic_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "findPlayersByNumbers", 4 | "params": { 5 | "numbers": [ 6 | "Vladimir", 7 | "Jack" 8 | ] 9 | }, 10 | "id": "7272" 11 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_generic_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "findPlayersByFirstNames", 4 | "params": { 5 | "names": [ 6 | { 7 | "name": "Vladimir" 8 | }, 9 | { 10 | "name": "Jack" 11 | } 12 | ] 13 | }, 14 | "id": "7272" 15 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByInitials", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos" 7 | }, 8 | "id": { 9 | "sha": "ca231d2" 10 | } 11 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_json.json: -------------------------------------------------------------------------------- 1 | { 2 | not_json 3 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_map_generic_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "getContractSums", 4 | "params": { 5 | "contractLengths": { 6 | "Backes": "4 years", 7 | "Tarasenko": 3, 8 | "Allen": 2, 9 | "Bouwmeester": 5, 10 | "Stamkos": 8, 11 | "Callahan": 3, 12 | "Bishop": 4, 13 | "Hedman": 2 14 | } 15 | }, 16 | "id": "7272" 17 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_method_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": { 4 | "name": "findByInitials" 5 | }, 6 | "params": { 7 | "firstName": "Steven", 8 | "lastName": "Stamkos" 9 | }, 10 | "id": "7272" 11 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_params_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "findByInitials", 4 | "params": "Steven Stamkos", 5 | "id": "7272" 6 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bad_version.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": { 3 | "version": "2.0" 4 | }, 5 | "method": "findByInitials", 6 | "params": { 7 | "firstName": "Steven", 8 | "lastName": "Stamkos" 9 | }, 10 | "id": "7272" 11 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/batch_error.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": 2.0, 4 | "method": "find_by_birth_year", 5 | "params": { 6 | "birthYear": "June 1990" 7 | }, 8 | "id": "7272" 9 | }, 10 | { 11 | "jsonrpc": 2.0, 12 | "method": "privateGetPlayers", 13 | "params": [], 14 | "id": "7273" 15 | }, 16 | { 17 | "jsonrpc": 2.0, 18 | "method": "findByInitials", 19 | "params": { 20 | "firstName": "Steven", 21 | "lastName": "Stamkos" 22 | }, 23 | "id": { 24 | "sha": "ca231d2" 25 | } 26 | }, 27 | { 28 | "jsonrpc": 2.0, 29 | "method": "findByCapHit", 30 | "params": { 31 | "cap": 4.5 32 | } 33 | } 34 | ] -------------------------------------------------------------------------------- /server/src/test/resources/error/request/bogus_service.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "bogus", 4 | "params": {}, 5 | "id": "7272" 6 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/mandatory_parameter_explicitly_null.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByInitials", 4 | "params": [ 5 | "Steven", 6 | null 7 | ], 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/mandatory_parameter_is_not_set.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByInitials", 4 | "params": { 5 | "firstName": "Steven", 6 | "number": 91 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/method_with_double_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "bogusFind", 4 | "params": { 5 | "firstName": "Steven", 6 | "age": 24 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/no_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "params": { 4 | "firstName": "Steven", 5 | "lastName": "Stamkos" 6 | }, 7 | "id": "7272" 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/no_version.json: -------------------------------------------------------------------------------- 1 | { 2 | "method": "findByInitials", 3 | "params": { 4 | "firstName": "Steven", 5 | "lastName": "Stamkos" 6 | }, 7 | "id": "7272" 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_annotated_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "bogusGetPlayers", 4 | "params": [], 5 | "id": "7272" 6 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_existed_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByName", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos" 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_implemented_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByCapHit", 4 | "params": { 5 | "cap": 4.5 6 | }, 7 | "id": "7272" 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_implemented_method_notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByCapHit", 4 | "params": { 5 | "cap": 4.5 6 | } 7 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_json_rpc.json: -------------------------------------------------------------------------------- 1 | { 2 | ":test": "data" 3 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_json_rpc_20.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 1.0, 3 | "method": "findByInitials", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos" 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_json_service.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "isAlive", 4 | "params": {}, 5 | "id": "7272" 6 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/not_public_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "privateGetPlayers", 4 | "params": [], 5 | "id": "7272" 6 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/param_annotation_is_not_specified.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "bogusFindByInitials", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos" 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/static_method.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "date", 4 | "params": { 5 | "textDate": "1984-05-01" 6 | }, 7 | "id": "7272" 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/unspecified_parameter.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "find", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos", 7 | "height": 181 8 | }, 9 | "id": "7272" 10 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_exception.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "login", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataFieldLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_getter.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataGetterLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_multiple_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataMultipleFieldsLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_multiple_getters.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataMultipleGettersLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_multiple_mixed.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataMultipleMixedLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_data_wrong_methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "errorDataWrongMethodsLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } 10 | -------------------------------------------------------------------------------- /server/src/test/resources/error/request/user_specified_error_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "bogusMessageLogin", 4 | "params": { 5 | "login": "secret", 6 | "password": "stuff" 7 | }, 8 | "id": "7272" 9 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/wrong_amount_of_arguments_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByInitials", 4 | "params": [ 5 | "Steven", 6 | "Stamkos", 7 | 91 8 | ], 9 | "id": "7272" 10 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/wrong_amount_of_arguments_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "findByInitials", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos", 7 | "number": 91 8 | }, 9 | "id": "7272" 10 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/wrong_parameter_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "find", 4 | "params": { 5 | "firstName": "Steven", 6 | "lastName": "Stamkos", 7 | "cap_hit": 7.5 8 | }, 9 | "id": "7272" 10 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/request/wrong_parameter_type.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": 2.0, 3 | "method": "find_by_birth_year", 4 | "params": { 5 | "birth_year": "June 1990" 6 | }, 7 | "id": "7272" 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/batch_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "jsonrpc": "2.0", 4 | "id": "7272", 5 | "error": { 6 | "code": -32602, 7 | "message": "Invalid params" 8 | } 9 | }, 10 | { 11 | "jsonrpc": "2.0", 12 | "id": "7273", 13 | "error": { 14 | "code": -32601, 15 | "message": "Method not found" 16 | } 17 | }, 18 | { 19 | "jsonrpc": "2.0", 20 | "id": null, 21 | "error": { 22 | "code": -32600, 23 | "message": "Invalid Request" 24 | } 25 | } 26 | ] -------------------------------------------------------------------------------- /server/src/test/resources/error/response/internal_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32603, 6 | "message": "Internal error" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/invalid_params.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32602, 6 | "message": "Invalid params" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/invalid_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": null, 4 | "error": { 5 | "code": -32600, 6 | "message": "Invalid Request" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/invalid_request_with_id.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32600, 6 | "message": "Invalid Request" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/method_not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32601, 6 | "message": "Method not found" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/parse_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": null, 4 | "error": { 5 | "code": -32700, 6 | "message": "Parse error" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/user_auth_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32032, 6 | "message": "You are not authorized to the team service" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/error/response/user_specified_error_data_field.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -30000, 6 | "message": "Error with data (field)", 7 | "data": [ 8 | "Data 1", 9 | "Data 2" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/test/resources/error/response/user_specified_error_data_getter.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -30001, 6 | "message": "Error with data (getter)", 7 | "data": [ 8 | "Data 1", 9 | "Data 2" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/test/resources/error/response/user_specified_error_data_wrong_methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -30005, 6 | "message": "Error with wrong methods", 7 | "data": [ 8 | "Data 1", 9 | "Data 2" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/test/resources/error/response/user_specified_error_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "id": "7272", 4 | "error": { 5 | "code": -32000, 6 | "message": "Not authorized" 7 | } 8 | } -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_1.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1} 2 | response={"jsonrpc": "2.0", "result": 19, "id": 1} 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_10.properties: -------------------------------------------------------------------------------- 1 | request=[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ] 2 | response={"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null} 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_11.properties: -------------------------------------------------------------------------------- 1 | request=[] 2 | response={"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null} 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_12.properties: -------------------------------------------------------------------------------- 1 | request=[1] 2 | response=[{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null} ] 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_13.properties: -------------------------------------------------------------------------------- 1 | request=[1,2,3] 2 | response=[ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}] 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_14.properties: -------------------------------------------------------------------------------- 1 | request=[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},{"foo": "boo"},{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"}, {"jsonrpc": "2.0", "method": "get_data", "id": "9"} ] 2 | response=[ {"jsonrpc": "2.0", "result": 7, "id": "1"}, {"jsonrpc": "2.0", "result": 19, "id": "2"}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"}, {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}] 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_15.properties: -------------------------------------------------------------------------------- 1 | request=[ {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]}, {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]} ] 2 | response="" 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_2.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2} 2 | response={"jsonrpc": "2.0", "result": -19, "id": 2} -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_3.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3} 2 | response={"jsonrpc": "2.0", "result": 19, "id": 3} -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_4.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4} 2 | response={"jsonrpc": "2.0", "result": 19, "id": 4} -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_5.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]} 2 | response="" -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_6.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "foobar"} 2 | response="" 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_7.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "foobar", "id": "1"} 2 | response={"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"} -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_8.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz] 2 | response={"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null} 3 | -------------------------------------------------------------------------------- /server/src/test/resources/spec/test_9.properties: -------------------------------------------------------------------------------- 1 | request={"jsonrpc": "2.0", "method": 1, "params": "bar"} 2 | response={"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null} 3 | --------------------------------------------------------------------------------