├── settings.gradle ├── .travis.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── RELEASE-NOTES.md ├── src ├── test │ ├── java │ │ └── com │ │ │ └── googlecode │ │ │ └── jsonrpc4j │ │ │ ├── spring │ │ │ ├── service │ │ │ │ ├── ServiceImpl.java │ │ │ │ └── Service.java │ │ │ ├── serviceb │ │ │ │ ├── package.java │ │ │ │ ├── Temperature.java │ │ │ │ ├── NoopTemperatureImpl.java │ │ │ │ └── TemperatureImpl.java │ │ │ ├── servicesansinterface │ │ │ │ └── ServiceSansInterfaceImpl.java │ │ │ ├── JsonRpcPathClientIntegrationTest.java │ │ │ ├── JsonRpcPathServerIntegrationTest.java │ │ │ ├── JsonRpcPathClientIntegrationTestB.java │ │ │ ├── JsonServiceExporterIntegrationTest.java │ │ │ └── JsonRpcPathServerIntegrationTestB.java │ │ │ ├── util │ │ │ ├── FakeTimingOutService.java │ │ │ ├── TestThrowable.java │ │ │ ├── CustomTestException.java │ │ │ ├── FakeTimingOutServiceImpl.java │ │ │ ├── FakeServiceInterface.java │ │ │ ├── FakeServiceInterfaceImpl.java │ │ │ ├── LocalThreadServer.java │ │ │ ├── BaseRestTest.java │ │ │ ├── JettyServer.java │ │ │ └── Util.java │ │ │ ├── server │ │ │ ├── JsonRpcServerSequentialBatchProcessingTest.java │ │ │ ├── JsonRpcServerParallelBatchProcessingTest.java │ │ │ ├── JsonRpcServerAnnotateMethodTest.java │ │ │ ├── MultiServiceTest.java │ │ │ ├── DefaultHttpStatusCodeProviderTest.java │ │ │ ├── HttpStatusCodeProviderTest.java │ │ │ ├── JsonRpcErrorsTest.java │ │ │ └── JsonRpcServerBatchTest.java │ │ │ ├── integration │ │ │ ├── TimeoutTest.java │ │ │ ├── SimpleTest.java │ │ │ ├── HttpClientTest.java │ │ │ ├── HttpCodeTest.java │ │ │ ├── ServerClientTest.java │ │ │ └── StreamServerTest.java │ │ │ └── client │ │ │ └── JsonRpcClientTest.java │ └── resources │ │ ├── clientApplicationContextB.xml │ │ ├── log4j2.xml │ │ ├── serverApplicationContext.xml │ │ ├── clientApplicationContext.xml │ │ ├── serverApplicationContextB.xml │ │ └── serverApplicationContextC.xml └── main │ └── java │ └── com │ └── googlecode │ └── jsonrpc4j │ ├── spring │ ├── JsonRpcReference.java │ ├── JsonServiceExporter.java │ ├── CompositeJsonServiceExporter.java │ ├── AutoJsonRpcServiceImpl.java │ ├── rest │ │ ├── SslClientHttpRequestFactory.java │ │ ├── JsonRpcResponseErrorHandler.java │ │ ├── JsonRestProxyFactoryBean.java │ │ └── MappingJacksonRPC2HttpMessageConverter.java │ ├── CompositeJsonStreamServiceExporter.java │ ├── JsonStreamServiceExporter.java │ ├── AutoJsonRpcClientProxyFactory.java │ ├── AutoJsonRpcClientProxyCreator.java │ ├── JsonProxyFactoryBean.java │ └── AbstractCompositeJsonServiceExporter.java │ ├── JsonRpcParamsPassMode.java │ ├── HttpException.java │ ├── JsonRpcFixedParams.java │ ├── RequestIDGenerator.java │ ├── ExceptionResolver.java │ ├── JsonRpcParam.java │ ├── JsonRpcFixedParam.java │ ├── JsonRpcErrors.java │ ├── JsonRpcService.java │ ├── VarArgsUtil.java │ ├── Util.java │ ├── JsonRpcCallback.java │ ├── RequestInterceptor.java │ ├── JsonRpcMethod.java │ ├── DefaultErrorResolver.java │ ├── StreamEndedException.java │ ├── JsonRpcClientException.java │ ├── JsonRpcError.java │ ├── ErrorData.java │ ├── ConvertedParameterTransformer.java │ ├── JsonResponse.java │ ├── HttpStatusCodeProvider.java │ ├── InvocationListener.java │ ├── NoCloseOutputStream.java │ ├── MultipleExceptionResolver.java │ ├── MultipleErrorResolver.java │ ├── JsonUtil.java │ ├── ReadContext.java │ ├── NoCloseInputStream.java │ ├── AnnotationsErrorResolver.java │ ├── MultipleInvocationListener.java │ ├── IJsonRpcClient.java │ ├── JsonRpcInterceptor.java │ ├── DefaultHttpStatusCodeProvider.java │ ├── ErrorResolver.java │ ├── JsonRpcMultiServer.java │ └── DefaultExceptionResolver.java ├── .github ├── dependabot.yml └── workflows │ ├── gradle-wrapper-validation.yml │ └── gradle-test-validation.yml ├── .gitignore ├── LICENSE ├── pom.gradle ├── gradlew.bat └── publishing.gradle /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jsonrpc4j' 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk17 4 | install: true 5 | script: 6 | - gradle build -i 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briandilley/jsonrpc4j/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | For a summary of releases and the changes in each release, please see: 2 | 3 | https://github.com/briandilley/jsonrpc4j/releases -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/service/ServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.service; 2 | 3 | public class ServiceImpl implements Service { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/FakeTimingOutService.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | public interface FakeTimingOutService { 4 | void doTimeout(); 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/service/Service.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.service; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcService; 4 | 5 | @JsonRpcService("TestService") 6 | public interface Service { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gradle 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/TestThrowable.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | @SuppressWarnings({"serial", "WeakerAccess"}) 4 | public class TestThrowable extends Throwable { 5 | public TestThrowable(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | validation: 6 | name: "Gradle wrapper validation" 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v5 10 | - uses: gradle/wrapper-validation-action@v3 11 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/JsonRpcReference.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target({ElementType.FIELD}) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Inherited 8 | public @interface JsonRpcReference { 9 | 10 | String address() default ""; 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/serviceb/package.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

This set of service classes is designed to test the 3 | * {@link com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter} bean used to 4 | * help with exposing JSON-RPC services. 5 | *

6 | */ 7 | 8 | package com.googlecode.jsonrpc4j.spring.serviceb; 9 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcParamsPassMode.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | * The JSON-RPC specification allows either passing parameters as an Array, for by-position arguments, or as an Object, 5 | * for by-name arguments. 6 | * 7 | */ 8 | public enum JsonRpcParamsPassMode { 9 | AUTO, 10 | ARRAY, 11 | OBJECT 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/CustomTestException.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | public class CustomTestException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public CustomTestException() { 8 | } 9 | 10 | public CustomTestException(String msg) { 11 | super(msg); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/serviceb/Temperature.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.serviceb; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcService; 4 | 5 | @JsonRpcService( 6 | "api/temperature" // note the absence of a leading slash 7 | ) 8 | public interface Temperature { 9 | 10 | @SuppressWarnings("unused") 11 | Integer currentTemperature(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/clientApplicationContextB.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/serviceb/NoopTemperatureImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.serviceb; 2 | 3 | /** 4 | *

This implementation should not be picked up by the 5 | * {@link com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter} 6 | * bean because it does not have the necessary annotation.

7 | */ 8 | 9 | public class NoopTemperatureImpl implements Temperature { 10 | 11 | @Override 12 | public Integer currentTemperature() { 13 | return 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d %p %c{1.}:%L [%t] %m %ex%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/HttpException.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Unchecked exception aimed to preserve error response body in case of http error. 7 | * 8 | * @author Alexander Makarov 9 | */ 10 | @SuppressWarnings("WeakerAccess") 11 | class HttpException extends RuntimeException { 12 | private static final long serialVersionUID = 1L; 13 | 14 | public HttpException(String message, IOException cause) { 15 | super(message, cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcFixedParams.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface JsonRpcFixedParams { 11 | 12 | /** 13 | * @return a fixed parameter's collection. 14 | */ 15 | JsonRpcFixedParam[] fixedParams(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/RequestIDGenerator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | * Request ID generator interface. This allows {@link JsonRpcClient} to use different strategy to 5 | * generate the ID for the request. 6 | */ 7 | public interface RequestIDGenerator { 8 | 9 | /** 10 | * Generate the request ID for each json-rpc request. 11 | * 12 | * @return The request id. This can be of any type. It is used to match the response with the request that it is replying to. 13 | */ 14 | String generateID(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/ExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | 5 | /** 6 | * Resolves client {@link Throwable}s from server generated {@link ObjectNode}. 7 | */ 8 | public interface ExceptionResolver { 9 | 10 | /** 11 | * Resolves the exception from the given json-rpc 12 | * response {@link ObjectNode}. 13 | * 14 | * @param response the response 15 | * @return the exception 16 | */ 17 | Throwable resolveException(ObjectNode response); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcParam.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 annotating service parameters as 10 | * JsonRpc params by name. 11 | */ 12 | @Target(ElementType.PARAMETER) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface JsonRpcParam { 15 | 16 | /** 17 | * @return the parameter's name. 18 | */ 19 | String value(); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/serverApplicationContext.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/FakeTimingOutServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class FakeTimingOutServiceImpl implements FakeTimingOutService { 7 | private static final Logger logger = LoggerFactory.getLogger(FakeTimingOutServiceImpl.class); 8 | 9 | @Override 10 | public void doTimeout() { 11 | try { 12 | Thread.sleep(10); 13 | } catch (InterruptedException e) { 14 | logger.debug("FakeTimingOutServiceImpl doTimeout() thread interrupted. Safe to ignore."); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcFixedParam.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface JsonRpcFixedParam { 11 | 12 | /** 13 | * @return the parameter's name. 14 | */ 15 | String name(); 16 | 17 | /** 18 | * @return the parameter's value. 19 | */ 20 | String value(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcErrors.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 holding an array of @JsonRpcError annotations 10 | * for a method. 11 | */ 12 | @SuppressWarnings("WeakerAccess") 13 | @Target(ElementType.METHOD) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface JsonRpcErrors { 16 | 17 | /** 18 | * @return the errors list. 19 | */ 20 | JsonRpcError[] value(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcService.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Annotation to define the path of a JSON-RPC service. 11 | */ 12 | @Target(TYPE) 13 | @Retention(RUNTIME) 14 | public @interface JsonRpcService { 15 | 16 | /** 17 | * The path that the service is available at. 18 | * 19 | * @return the service path 20 | */ 21 | String value(); 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/serviceb/TemperatureImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.serviceb; 2 | 3 | import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImpl; 4 | 5 | /** 6 | *

This implementation should be picked up by the 7 | * {@link com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter} 8 | * class.

9 | */ 10 | 11 | @AutoJsonRpcServiceImpl(additionalPaths = { 12 | "/api-web/temperature" // note the leading slash 13 | }) 14 | public class TemperatureImpl implements Temperature { 15 | 16 | @Override 17 | public Integer currentTemperature() { 18 | return 25; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/JsonRpcServerSequentialBatchProcessingTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcServer; 4 | import com.googlecode.jsonrpc4j.util.Util; 5 | import org.easymock.EasyMockRunner; 6 | import org.junit.Before; 7 | import org.junit.runner.RunWith; 8 | 9 | @RunWith(EasyMockRunner.class) 10 | public class JsonRpcServerSequentialBatchProcessingTest extends JsonRpcServerBatchTest { 11 | 12 | @Before 13 | public void setup() { 14 | jsonRpcServer = new JsonRpcServer(Util.mapper, mockService, JsonRpcServerTest.ServiceInterface.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/servicesansinterface/ServiceSansInterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.servicesansinterface; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcService; 4 | 5 | /** 6 | *

Unlike the {@link com.googlecode.jsonrpc4j.spring.service.Service} / 7 | * {@link com.googlecode.jsonrpc4j.spring.service.ServiceImpl} example, this case has no interface 8 | * so the bean has the @JsonRpcService directly into the implementation. This setup worked 9 | * in jsonrpc4j 1.1, but failed in 1.2.

10 | */ 11 | 12 | @JsonRpcService("ServiceSansInterface") 13 | public class ServiceSansInterfaceImpl { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/clientApplicationContext.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/serverApplicationContextB.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/VarArgsUtil.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * 20.04.2016 8 | * KostaPC 9 | */ 10 | 11 | public class VarArgsUtil { 12 | 13 | public static Map convertArgs(Object[] params) { 14 | 15 | final Map unsafeMap = new HashMap<>(); 16 | for (int i = 0; i < params.length; i += 2) { 17 | if (params[i] instanceof String && params[i] != null && !params[i].toString().isEmpty()) { 18 | unsafeMap.put(params[i].toString(), params[i + 1]); 19 | } 20 | } 21 | return unsafeMap; 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | # Mobile Tools for Java (J2ME) 3 | # Package Files # 4 | *.jar 5 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 6 | ### NetBeans template 7 | build/ 8 | ### Maven template 9 | ### Eclipse template 10 | .gradle 11 | gradle.properties 12 | tmp/ 13 | # Eclipse Core 14 | # External tool builders 15 | # Locally stored "Eclipse launch configurations" 16 | # CDT-specific 17 | # JDT-specific (Eclipse Java Development Tools) 18 | # Java annotation processor (APT) 19 | ### JetBrains template 20 | *.iml 21 | .idea/ 22 | # Ignore Eclipse generated files 23 | .classpath 24 | .project 25 | .settings/ 26 | /bin/ 27 | /.nb-gradle/ 28 | /target 29 | out/ 30 | .DS_Store 31 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/Util.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | 5 | public class Util { 6 | 7 | @SuppressWarnings("PMD.AvoidUsingHardCodedIP") 8 | public static final String DEFAULT_HOSTNAME = "0.0.0.0"; 9 | 10 | static boolean hasNonNullObjectData(final ObjectNode node, final String key) { 11 | return hasNonNullData(node, key) && node.get(key).isObject(); 12 | } 13 | 14 | static boolean hasNonNullData(final ObjectNode node, final String key) { 15 | return node.has(key) && !node.get(key).isNull(); 16 | } 17 | 18 | static boolean hasNonNullTextualData(final ObjectNode node, final String key) { 19 | return hasNonNullData(node, key) && node.get(key).isTextual(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/serverApplicationContextC.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/FakeServiceInterface.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | 6 | @SuppressWarnings("unused") 7 | public interface FakeServiceInterface { 8 | void doSomething(); 9 | 10 | int returnPrimitiveInt(int arg); 11 | 12 | CustomClass returnCustomClass(int arg1, String arg2); 13 | 14 | void throwSomeException(String message); 15 | 16 | class CustomClass { 17 | 18 | public final int integer; 19 | public final String string; 20 | public final Collection list = new ArrayList<>(); 21 | 22 | public CustomClass() { 23 | this(0, ""); 24 | } 25 | 26 | CustomClass(final int integer, final String string) { 27 | this.integer = integer; 28 | this.string = string; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcCallback.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | * This interface is used by the JsonRpcHttpAsyncClient for receiving 5 | * RPC responses. When an invocation is made, one of {@code onComplete()} 6 | * or {@code onError()} is guaranteed to be called. 7 | * 8 | * @param the return type of the JSON-RPC call 9 | * @author Brett Wooldridge 10 | */ 11 | public interface JsonRpcCallback { 12 | 13 | /** 14 | * Called if the remote invocation was successful. 15 | * 16 | * @param result the result object of the call (possibly null) 17 | */ 18 | void onComplete(T result); 19 | 20 | /** 21 | * Called if there was an error in the remove invocation. 22 | * 23 | * @param t the {@code Throwable} (possibly wrapping) the invocation error 24 | */ 25 | void onError(Throwable t); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/RequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | /** 6 | * Implementations of this interface are able to intercept Requests by throwing Exceptions. This allows for 7 | * implementation of Authentication, Authorization and Sanitizing of the Request. 8 | * 9 | * @author fredo 10 | */ 11 | @SuppressWarnings({"unused", "WeakerAccess"}) 12 | public interface RequestInterceptor { 13 | 14 | /** 15 | * This method will be invoked prior to any JSON-RPC service being invoked. If an exception is thrown an error will 16 | * be sent back to the client. 17 | * 18 | * @param request is the request object. 19 | * @throws Throwable a exception that stops the request 20 | */ 21 | void interceptRequest(JsonNode request) throws Throwable; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcMethod.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 annotating service methods as 10 | * JsonRpc method by name. 11 | */ 12 | @Target(ElementType.METHOD) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface JsonRpcMethod { 15 | 16 | /** 17 | * @return the method's name. 18 | */ 19 | String value(); 20 | 21 | JsonRpcParamsPassMode paramsPassMode() default JsonRpcParamsPassMode.AUTO; 22 | 23 | /** 24 | * If {@code true}, the Java method name will not be used to resolve rpc calls. 25 | * @return whether the {@link #value()} is required to match the method. 26 | */ 27 | boolean required() default false; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/DefaultErrorResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.ERROR_NOT_HANDLED; 9 | 10 | /** 11 | * An {@link ErrorResolver} that puts type information into the 12 | * data portion of the error. This {@link ErrorResolver} always 13 | * returns a {@link com.googlecode.jsonrpc4j.ErrorResolver.JsonError JsonError}. 14 | */ 15 | @SuppressWarnings("WeakerAccess") 16 | public enum DefaultErrorResolver implements ErrorResolver { 17 | INSTANCE; 18 | 19 | /** 20 | * {@inheritDoc} 21 | */ 22 | public JsonError resolveError(Throwable t, Method method, List arguments) { 23 | return new JsonError(ERROR_NOT_HANDLED.code, t.getMessage(), new ErrorData(t.getClass().getName(), t.getMessage())); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/gradle-test-validation.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Gradle Test Validation 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | validate: 13 | name: Validate with Gradle Tests 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v5 19 | 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v5 22 | with: 23 | distribution: 'temurin' 24 | java-version: '17' 25 | 26 | - name: Grant execute permission to Gradle wrapper 27 | run: chmod +x ./gradlew 28 | 29 | - name: Run Gradle tests 30 | run: ./gradlew test 31 | 32 | - name: Upload test results (optional) 33 | if: always() 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: test-results 37 | path: build/test-results 38 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/StreamEndedException.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * @author brian 7 | */ 8 | @SuppressWarnings({"serial", "WeakerAccess", "unused"}) 9 | class StreamEndedException extends IOException { 10 | 11 | public StreamEndedException() { 12 | } 13 | 14 | /** 15 | * @param message the detail message 16 | */ 17 | public StreamEndedException(String message) { 18 | super(message); 19 | } 20 | 21 | /** 22 | * @param cause the cause (a null value is permitted, and indicates that the cause is nonexistent or unknown) 23 | */ 24 | public StreamEndedException(Throwable cause) { 25 | super(cause); 26 | } 27 | 28 | /** 29 | * @param message the detail message 30 | * @param cause the cause (a null value is permitted, and indicates that the cause is nonexistent or unknown) 31 | */ 32 | public StreamEndedException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/FakeServiceInterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Collections; 7 | 8 | @SuppressWarnings("WeakerAccess") 9 | public class FakeServiceInterfaceImpl implements FakeServiceInterface { 10 | 11 | private static final Logger logger = LoggerFactory.getLogger(FakeServiceInterfaceImpl.class); 12 | 13 | @Override 14 | public void doSomething() { 15 | logger.debug("doing something"); 16 | } 17 | 18 | @Override 19 | public int returnPrimitiveInt(int arg) { 20 | return arg; 21 | } 22 | 23 | @Override 24 | public CustomClass returnCustomClass(int primitiveArg, String stringArg) { 25 | CustomClass result = new CustomClass(primitiveArg, stringArg); 26 | Collections.addAll(result.list, "" + primitiveArg, stringArg); 27 | return result; 28 | } 29 | 30 | @Override 31 | public void throwSomeException(String message) { 32 | throw new UnsupportedOperationException(message); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcClientException.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | /** 6 | * Unchecked Exception thrown by a JSON-RPC client when 7 | * an error occurs. 8 | */ 9 | @SuppressWarnings({"serial", "unused", "WeakerAccess"}) 10 | public class JsonRpcClientException extends RuntimeException { 11 | 12 | private final int code; 13 | private final JsonNode data; 14 | 15 | /** 16 | * Creates the exception. 17 | * 18 | * @param code the code from the server 19 | * @param message the message from the server 20 | * @param data the data from the server 21 | */ 22 | public JsonRpcClientException(int code, String message, JsonNode data) { 23 | super(message); 24 | this.code = code; 25 | this.data = data; 26 | } 27 | 28 | /** 29 | * @return the code 30 | */ 31 | public int getCode() { 32 | return code; 33 | } 34 | 35 | /** 36 | * @return the data 37 | */ 38 | public JsonNode getData() { 39 | return data; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcError.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 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 mapping exceptions in service methods to custom JsonRpc errors. 10 | */ 11 | @SuppressWarnings("WeakerAccess") 12 | @Target(ElementType.ANNOTATION_TYPE) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface JsonRpcError { 15 | 16 | /** 17 | * @return the exception handled by the annotation. 18 | */ 19 | Class exception(); 20 | 21 | /** 22 | * @return the exception code 23 | */ 24 | int code(); 25 | 26 | /** 27 | * @return the error message. 28 | */ 29 | String message() default ""; 30 | 31 | /** 32 | * @return the ata. If data is not specified, message from exception will be used. If the message is null, 33 | * the data element be omitted in the error. 34 | */ 35 | String data() default ""; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/ErrorData.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | * Data that is added to an error. 5 | */ 6 | @SuppressWarnings({"unused", "WeakerAccess"}) 7 | public class ErrorData { 8 | 9 | private final String exceptionTypeName; 10 | private final String message; 11 | 12 | /** 13 | * Creates it. 14 | * 15 | * @param exceptionTypeName the exception type name 16 | * @param message the message 17 | */ 18 | public ErrorData(String exceptionTypeName, String message) { 19 | this.exceptionTypeName = exceptionTypeName; 20 | this.message = message; 21 | } 22 | 23 | /** 24 | * @return the exceptionTypeName 25 | */ 26 | public String getExceptionTypeName() { 27 | return exceptionTypeName; 28 | } 29 | 30 | /** 31 | * @return the message 32 | */ 33 | public String getMessage() { 34 | return message; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "ErrorData{" + "exceptionTypeName='" + exceptionTypeName + '\'' + 40 | ", message='" + message + '\'' + 41 | '}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/JsonRpcServerParallelBatchProcessingTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcServer; 4 | import com.googlecode.jsonrpc4j.util.Util; 5 | import org.easymock.EasyMockRunner; 6 | import org.junit.Before; 7 | import org.junit.runner.RunWith; 8 | 9 | import java.util.concurrent.ArrayBlockingQueue; 10 | import java.util.concurrent.ThreadPoolExecutor; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | @RunWith(EasyMockRunner.class) 14 | public class JsonRpcServerParallelBatchProcessingTest extends JsonRpcServerBatchTest { 15 | 16 | @Before 17 | public void setup() { 18 | jsonRpcServer = new JsonRpcServer(Util.mapper, mockService, JsonRpcServerTest.ServiceInterface.class); 19 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 20 | 50, 21 | 1000, 22 | TimeUnit.MILLISECONDS, 23 | new ArrayBlockingQueue<>(50)); 24 | jsonRpcServer.setBatchExecutorService(threadPoolExecutor); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcServer; 4 | import org.springframework.web.HttpRequestHandler; 5 | 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * {@link HttpRequestHandler} that exports user services using JSON-RPC over HTTP protocol 13 | */ 14 | public class JsonServiceExporter extends AbstractJsonServiceExporter implements HttpRequestHandler { 15 | 16 | private JsonRpcServer jsonRpcServer; 17 | 18 | /** 19 | * {@inheritDoc} 20 | */ 21 | @Override 22 | protected void exportService() { 23 | jsonRpcServer = getJsonRpcServer(); 24 | } 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 30 | jsonRpcServer.handle(request, response); 31 | response.getOutputStream().flush(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/TimeoutTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcHttpClient; 4 | import com.googlecode.jsonrpc4j.ProxyUtil; 5 | import com.googlecode.jsonrpc4j.util.BaseRestTest; 6 | import com.googlecode.jsonrpc4j.util.FakeTimingOutService; 7 | import com.googlecode.jsonrpc4j.util.FakeTimingOutServiceImpl; 8 | import org.junit.Test; 9 | 10 | import java.net.SocketTimeoutException; 11 | 12 | import static org.hamcrest.CoreMatchers.isA; 13 | 14 | public class TimeoutTest extends BaseRestTest { 15 | private FakeTimingOutService service; 16 | 17 | @Test 18 | public void testTimingOutRequests() throws Exception { 19 | JsonRpcHttpClient client = getHttpClient(true, false); 20 | client.setReadTimeoutMillis(1); 21 | expectedEx.expectCause(isA(SocketTimeoutException.class)); 22 | service = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), FakeTimingOutService.class, client); 23 | service.doTimeout(); 24 | } 25 | 26 | @Override 27 | protected Class service() { 28 | return FakeTimingOutServiceImpl.class; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Brian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/ConvertedParameterTransformer.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | * Implementations of this interface are able to transform the converted parameters before a method invocation in the 5 | * JSON-RPC service. This allows for mutation of the deserialized arguments before a method invocation or for validation 6 | * of the actual argument objects. 7 | *

8 | * Any exceptions thrown in the {@link ConvertedParameterTransformer#transformConvertedParameters(Object, Object[])} 9 | * method, will be returned as an error to the JSON-RPC client. 10 | */ 11 | public interface ConvertedParameterTransformer { 12 | 13 | /** 14 | * Returns the parameters that will be passed to the method on invocation. 15 | * 16 | * @param target optional service name used to locate the target object 17 | * to invoke the Method on. 18 | * @param convertedParams the parameters to pass to the method on invocation. 19 | * @return the mutated parameters that will be passed to the method on invocation. 20 | */ 21 | Object[] transformConvertedParameters(Object target, Object[] convertedParams); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/CompositeJsonServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcServer; 4 | import org.springframework.web.HttpRequestHandler; 5 | 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * A Composite service exporter for spring that exposes 13 | * multiple services via JSON-RPC over HTTP. 14 | */ 15 | @SuppressWarnings("unused") 16 | public class CompositeJsonServiceExporter extends AbstractCompositeJsonServiceExporter implements HttpRequestHandler { 17 | 18 | private JsonRpcServer jsonRpcServer; 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | @Override 24 | protected void exportService() { 25 | jsonRpcServer = getJsonRpcServer(); 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 32 | jsonRpcServer.handle(request, response); 33 | response.getOutputStream().flush(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonResponse.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | /** 6 | * Contains the JSON-RPC answer in {@code response} 7 | * {@code exceptionToRethrow} contains exception, which should be thrown when property {@code rethrowExceptions} 8 | * is active 9 | */ 10 | public class JsonResponse { 11 | private JsonNode response; 12 | private int code; 13 | private RuntimeException exceptionToRethrow; 14 | 15 | public JsonResponse() { 16 | } 17 | 18 | public JsonResponse(JsonNode response, int code) { 19 | this.response = response; 20 | this.code = code; 21 | } 22 | 23 | public JsonNode getResponse() { 24 | return response; 25 | } 26 | 27 | public void setResponse(JsonNode response) { 28 | this.response = response; 29 | } 30 | 31 | public int getCode() { 32 | return code; 33 | } 34 | 35 | public void setCode(int code) { 36 | this.code = code; 37 | } 38 | 39 | public RuntimeException getExceptionToRethrow() { 40 | return exceptionToRethrow; 41 | } 42 | 43 | public void setExceptionToRethrow(RuntimeException exceptionToRethrow) { 44 | this.exceptionToRethrow = exceptionToRethrow; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/JsonRpcPathClientIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.spring.service.Service; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.aop.support.AopUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | 11 | import static org.junit.Assert.assertNotNull; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @ContextConfiguration("classpath:clientApplicationContext.xml") 16 | public class JsonRpcPathClientIntegrationTest { 17 | 18 | @Autowired 19 | private Service service; 20 | 21 | @Test 22 | public void shouldCreateServiceExporter() { 23 | assertNotNull(service); 24 | assertTrue(AopUtils.isAopProxy(service)); 25 | } 26 | 27 | @Test 28 | public void callToObjectMethodsShouldBeHandledLocally() { 29 | if (service != null) { 30 | assertNotNull(service.toString()); 31 | // noinspection ResultOfMethodCallIgnored 32 | service.hashCode(); 33 | // noinspection EqualsWithItself 34 | assertTrue(service.equals(service)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/JsonRpcPathServerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | import static org.junit.Assert.assertNotNull; 11 | import static org.junit.Assert.assertSame; 12 | 13 | /** 14 | * @deprecated this test should be removed (replaced by {@link JsonRpcPathServerIntegrationTestB}) 15 | * once the {@link AutoJsonRpcServiceExporter} is dropped. 16 | */ 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @ContextConfiguration("classpath:serverApplicationContext.xml") 20 | @Deprecated 21 | public class JsonRpcPathServerIntegrationTest { 22 | 23 | @Autowired 24 | private ApplicationContext applicationContext; 25 | 26 | @Test 27 | public void shouldCreateServiceExporter() { 28 | assertNotNull(applicationContext); 29 | 30 | { 31 | Object bean = applicationContext.getBean("/TestService"); 32 | assertSame(JsonServiceExporter.class, bean.getClass()); 33 | } 34 | 35 | { 36 | Object bean = applicationContext.getBean("/ServiceSansInterface"); 37 | assertSame(JsonServiceExporter.class, bean.getClass()); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * This annotation goes on the implementation of the JSON-RPC service. It defines any additional paths on 11 | * which the JSON-RPC service should be exported. This can be used with the {@link AutoJsonRpcServiceImplExporter} 12 | * in order to automatically expose the JSON-RPC services in a spring based web application server. Note that the 13 | * implementation should still continue to carry the {@link com.googlecode.jsonrpc4j.JsonRpcServer} annotation; 14 | * preferably on the service interface. 15 | */ 16 | 17 | @Target(TYPE) 18 | @Retention(RUNTIME) 19 | public @interface AutoJsonRpcServiceImpl { 20 | 21 | /** 22 | * This value may contain a list of additional paths that the JSON-RPC service will be exposed on. 23 | * These are in addition to any which are defined on the {@link com.googlecode.jsonrpc4j.JsonRpcService} 24 | * annotation preferably on the service interface. This might be used, for example, where you still want 25 | * to expose a service on legacy paths for older clients. 26 | * 27 | * @return an array of additional paths 28 | */ 29 | 30 | String[] additionalPaths() default {}; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/JsonRpcPathClientIntegrationTestB.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.spring.serviceb.Temperature; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.aop.support.AopUtils; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | import static org.junit.Assert.assertNotNull; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | @RunWith(SpringJUnit4ClassRunner.class) 14 | @ContextConfiguration("classpath:clientApplicationContextB.xml") 15 | public class JsonRpcPathClientIntegrationTestB { 16 | 17 | @JsonRpcReference(address = "http://localhost:8080") 18 | private Temperature temperature; 19 | 20 | public Integer demo() { 21 | return temperature.currentTemperature(); 22 | } 23 | 24 | 25 | @Test 26 | public void shouldCreateServiceExporter() { 27 | assertNotNull(temperature); 28 | assertTrue(AopUtils.isAopProxy(temperature)); 29 | } 30 | 31 | @Test 32 | public void callToObjectMethodsShouldBeHandledLocally() { 33 | if (temperature != null) { 34 | assertNotNull(temperature.toString()); 35 | // noinspection ResultOfMethodCallIgnored 36 | temperature.hashCode(); 37 | // noinspection EqualsWithItself 38 | assertTrue(temperature.equals(temperature)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/SimpleTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.googlecode.jsonrpc4j.ProxyUtil; 4 | import com.googlecode.jsonrpc4j.util.BaseRestTest; 5 | import com.googlecode.jsonrpc4j.util.FakeServiceInterface; 6 | import com.googlecode.jsonrpc4j.util.FakeServiceInterfaceImpl; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class SimpleTest extends BaseRestTest { 14 | private FakeServiceInterface service; 15 | 16 | @Before 17 | @Override 18 | public void setup() throws Exception { 19 | super.setup(); 20 | service = ProxyUtil.createClientProxy(FakeServiceInterface.class, getClient()); 21 | } 22 | 23 | @Override 24 | protected Class service() { 25 | return FakeServiceInterfaceImpl.class; 26 | } 27 | 28 | @Test 29 | public void doSomething() { 30 | service.doSomething(); 31 | } 32 | 33 | @Test 34 | public void returnPrimitiveInt() { 35 | final int at = 22; 36 | assertEquals(at, service.returnPrimitiveInt(at)); 37 | } 38 | 39 | @Test 40 | public void returnCustomClass() { 41 | final int at = 22; 42 | final String message = "simple"; 43 | FakeServiceInterface.CustomClass result = service.returnCustomClass(at, message); 44 | assertEquals(at, result.integer); 45 | assertEquals(message, result.string); 46 | assertTrue(result.list.contains(message)); 47 | assertTrue(result.list.contains("" + at)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/HttpStatusCodeProvider.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | /** 4 | *

5 | * A status code provider maps an HTTP status code to a JSON-RPC result code (e.g. -32000 -> 500). 6 | *

7 | *

8 | * From version 2.0 on the JSON-RPC specification is not explicitly documenting the mapping of result/error codes, so 9 | * this provider can be used to configure application specific HTTP status codes for a given JSON-RPC error code. 10 | *

11 | *

12 | * The default implementation {@link DefaultHttpStatusCodeProvider} follows the rules defined in the 13 | * JSON-RPC over HTTP document. 14 | *

15 | */ 16 | public interface HttpStatusCodeProvider { 17 | 18 | /** 19 | * Returns an HTTP status code for the given response and result code. 20 | * 21 | * @param resultCode the result code of the current JSON-RPC method call. This is used to look up the HTTP status 22 | * code. 23 | * @return the int representation of the HTTP status code that should be used by the JSON-RPC response. 24 | */ 25 | int getHttpStatusCode(int resultCode); 26 | 27 | /** 28 | * Returns result code for the given HTTP status code 29 | * 30 | * @param httpStatusCode the int representation of the HTTP status code that should be used by the JSON-RPC response. 31 | * @return resultCode the result code of the current JSON-RPC method call. This is used to look up the HTTP status 32 | */ 33 | Integer getJsonRpcCode(int httpStatusCode); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/rest/SslClientHttpRequestFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.rest; 2 | 3 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 4 | 5 | import javax.net.ssl.HostnameVerifier; 6 | import javax.net.ssl.HttpsURLConnection; 7 | import javax.net.ssl.SSLContext; 8 | import java.io.IOException; 9 | import java.net.HttpURLConnection; 10 | 11 | /** 12 | * Implementation of {@link org.springframework.http.client.ClientHttpRequestFactory} that creates HTTPS connection 13 | * with specified settings. 14 | */ 15 | class SslClientHttpRequestFactory 16 | extends SimpleClientHttpRequestFactory { 17 | 18 | private SSLContext sslContext; 19 | private HostnameVerifier hostNameVerifier; 20 | 21 | @Override 22 | protected void prepareConnection(HttpURLConnection connection, String httpMethod) 23 | throws IOException { 24 | 25 | if (connection instanceof HttpsURLConnection) { 26 | final HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; 27 | 28 | if (hostNameVerifier != null) { 29 | httpsConnection.setHostnameVerifier(hostNameVerifier); 30 | } 31 | 32 | if (sslContext != null) { 33 | httpsConnection.setSSLSocketFactory(sslContext.getSocketFactory()); 34 | } 35 | } 36 | 37 | super.prepareConnection(connection, httpMethod); 38 | } 39 | 40 | public void setSslContext(SSLContext sslContext) { 41 | this.sslContext = sslContext; 42 | } 43 | 44 | public void setHostNameVerifier(HostnameVerifier hostNameVerifier) { 45 | this.hostNameVerifier = hostNameVerifier; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/InvocationListener.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * Implementations of this interface are able to be informed when JSON-RPC services are invoked. This allows for 10 | * instrumentation of the invocations so that statistics about the invocations can be recorded and reported on. 11 | * 12 | * @author Andrew Lindesay 13 | */ 14 | 15 | public interface InvocationListener { 16 | 17 | /** 18 | * This method will be invoked prior to a JSON-RPC service being invoked. 19 | * 20 | * @param method is the method that will be invoked. 21 | * @param arguments are the arguments that will be passed to the method when it is invoked. 22 | */ 23 | 24 | void willInvoke(Method method, List arguments); 25 | 26 | /** 27 | * This method will be invoked after a JSON-RPC service has been invoked. 28 | * 29 | * @param t is the throwable that was thrown from the invocation, if no error arose, this value 30 | * will be null. 31 | * @param result is the result of the method invocation. If an error arose, this value will be 32 | * null. 33 | * @param method is the method that will was invoked. 34 | * @param arguments are the arguments that were be passed to the method when it is invoked. 35 | * @param duration is approximately the number of milliseconds that elapsed during which the method was invoked. 36 | */ 37 | 38 | void didInvoke(Method method, List arguments, Object result, Throwable t, long duration); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/NoCloseOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | @SuppressWarnings({"WeakerAccess", "unused"}) 7 | class NoCloseOutputStream extends OutputStream { 8 | 9 | private final OutputStream ops; 10 | private boolean closeAttempted = false; 11 | 12 | public NoCloseOutputStream(OutputStream ops) { 13 | this.ops = ops; 14 | } 15 | 16 | /** 17 | * {@inheritDoc} 18 | */ 19 | @Override 20 | public void write(int b) throws IOException { 21 | this.ops.write(b); 22 | } 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public void write(byte[] b) throws IOException { 29 | this.ops.write(b); 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public void write(byte[] b, int off, int len) throws IOException { 37 | // Validate input parameters 38 | if (b == null) { 39 | throw new NullPointerException("Input byte array cannot be null"); 40 | } 41 | 42 | if (off < 0 || len < 0 || off + len > b.length) { 43 | throw new ArrayIndexOutOfBoundsException("Invalid offset or length parameters"); 44 | } 45 | 46 | // Only perform the write operation after validation 47 | this.ops.write(b, off, len); 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | public void flush() throws IOException { 55 | this.ops.flush(); 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public void close() throws IOException { 63 | closeAttempted = true; 64 | } 65 | 66 | /** 67 | * @return the closeAttempted 68 | */ 69 | public boolean wasCloseAttempted() { 70 | return closeAttempted; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/MultipleExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | 5 | import java.util.Collections; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | /** 10 | * {@link ExceptionResolver} that supports the use 11 | * of multiple {@link ExceptionResolver} used one 12 | * after another until one is able to resolve 13 | * the Exception. 14 | */ 15 | @SuppressWarnings({"unused", "WeakerAccess"}) 16 | public class MultipleExceptionResolver implements ExceptionResolver { 17 | 18 | private final List resolvers; 19 | 20 | /** 21 | * Creates with the given {@link ExceptionResolver}s, 22 | * {@link #addExceptionResolver(ExceptionResolver)} can be called to 23 | * add additional {@link ExceptionResolver}s. 24 | * 25 | * @param resolvers the {@link ExceptionResolver}s 26 | */ 27 | public MultipleExceptionResolver(ExceptionResolver... resolvers) { 28 | this.resolvers = new LinkedList<>(); 29 | Collections.addAll(this.resolvers, resolvers); 30 | } 31 | 32 | /** 33 | * Adds an {@link ExceptionResolver} to the end of the 34 | * resolver chain. 35 | * 36 | * @param ExceptionResolver the {@link ExceptionResolver} to add 37 | */ 38 | public void addExceptionResolver(ExceptionResolver ExceptionResolver) { 39 | this.resolvers.add(ExceptionResolver); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public Throwable resolveException(ObjectNode response) { 46 | for (ExceptionResolver resolver : resolvers) { 47 | Throwable resolvedException = resolver.resolveException(response); 48 | if (resolvedException != null) { 49 | return resolvedException; 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/MultipleErrorResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Collections; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | /** 11 | * {@link ErrorResolver} that supports the use 12 | * of multiple {@link ErrorResolver} used one 13 | * after another until one is able to resolve 14 | * the error. 15 | */ 16 | @SuppressWarnings({"WeakerAccess", "unused"}) 17 | public class MultipleErrorResolver implements ErrorResolver { 18 | 19 | private final List resolvers; 20 | 21 | /** 22 | * Creates with the given {@link ErrorResolver}s, 23 | * {@link #addErrorResolver(ErrorResolver)} can be called to 24 | * add additional {@link ErrorResolver}s. 25 | * 26 | * @param resolvers the {@link ErrorResolver}s 27 | */ 28 | public MultipleErrorResolver(ErrorResolver... resolvers) { 29 | this.resolvers = new LinkedList<>(); 30 | Collections.addAll(this.resolvers, resolvers); 31 | } 32 | 33 | /** 34 | * Adds an {@link ErrorResolver} to the end of the 35 | * resolver chain. 36 | * 37 | * @param errorResolver the {@link ErrorResolver} to add 38 | */ 39 | public void addErrorResolver(ErrorResolver errorResolver) { 40 | this.resolvers.add(errorResolver); 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | @Override 47 | public JsonError resolveError(Throwable t, Method method, List arguments) { 48 | 49 | JsonError resolvedError; 50 | for (ErrorResolver resolver : resolvers) { 51 | resolvedError = resolver.resolveError(t, method, arguments); 52 | if (resolvedError != null) { 53 | return resolvedError; 54 | } 55 | } 56 | return null; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.*; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | import java.util.IdentityHashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public abstract class JsonUtil { 13 | private static final Map, Class> numericNodesMap = new IdentityHashMap<>(7); 14 | 15 | static { 16 | numericNodesMap.put(BigIntegerNode.class, BigInteger.class); 17 | numericNodesMap.put(DecimalNode.class, BigDecimal.class); 18 | numericNodesMap.put(DoubleNode.class, Double.class); 19 | numericNodesMap.put(FloatNode.class, Float.class); 20 | numericNodesMap.put(IntNode.class, Integer.class); 21 | numericNodesMap.put(LongNode.class, Long.class); 22 | numericNodesMap.put(ShortNode.class, Short.class); 23 | } 24 | 25 | public static Class getJavaTypeForNumericJsonType(Class node) { 26 | return numericNodesMap.get(node); 27 | } 28 | 29 | public static Class getJavaTypeForNumericJsonType(NumericNode node) { 30 | return getJavaTypeForNumericJsonType(node.getClass()); 31 | } 32 | 33 | public static Class getJavaTypeForJsonType(JsonNode node) { 34 | JsonNodeType jsonType = node.getNodeType(); 35 | 36 | switch (jsonType) { 37 | case ARRAY: 38 | return List.class; 39 | case BINARY: 40 | return Object.class; 41 | case BOOLEAN: 42 | return Boolean.class; 43 | case MISSING: 44 | return Object.class; 45 | case NULL: 46 | return Object.class; 47 | case NUMBER: 48 | return getJavaTypeForNumericJsonType((NumericNode) node); 49 | case OBJECT: 50 | return Object.class; 51 | case POJO: 52 | return Object.class; 53 | case STRING: 54 | return String.class; 55 | default: 56 | return Object.class; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/HttpClientTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcClientException; 4 | import com.googlecode.jsonrpc4j.ProxyUtil; 5 | import com.googlecode.jsonrpc4j.util.BaseRestTest; 6 | import com.googlecode.jsonrpc4j.util.FakeServiceInterface; 7 | import com.googlecode.jsonrpc4j.util.FakeServiceInterfaceImpl; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import java.net.MalformedURLException; 12 | 13 | import static org.hamcrest.CoreMatchers.containsString; 14 | import static org.hamcrest.CoreMatchers.equalTo; 15 | 16 | /** 17 | * HttpClientTest 18 | */ 19 | public class HttpClientTest extends BaseRestTest { 20 | 21 | private FakeServiceInterface service; 22 | 23 | @Test 24 | public void testRequestAndResponse() throws MalformedURLException { 25 | service = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), FakeServiceInterface.class, getHttpClient(false, false)); 26 | int i = service.returnPrimitiveInt(2); 27 | Assert.assertEquals(2, i); 28 | } 29 | 30 | @Test 31 | public void testCustomException() throws Exception { 32 | expectedEx.expectMessage(equalTo("Custom exception")); 33 | expectedEx.expect(JsonRpcClientException.class); 34 | 35 | service = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), FakeServiceInterface.class, getHttpClient(false, false)); 36 | service.throwSomeException("Custom exception"); 37 | } 38 | 39 | @Test 40 | public void testHttpError() throws Exception { 41 | expectedEx.expectMessage(containsString("405 HTTP method POST is not supported by this URL")); 42 | expectedEx.expect(Exception.class); 43 | 44 | service = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), FakeServiceInterface.class, getHttpClient("error", false, false)); 45 | service.doSomething(); 46 | } 47 | 48 | @Override 49 | protected Class service() { 50 | return FakeServiceInterfaceImpl.class; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/ReadContext.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.SocketException; 9 | 10 | @SuppressWarnings("WeakerAccess") 11 | public class ReadContext { 12 | 13 | private final InputStream input; 14 | private final ObjectMapper mapper; 15 | 16 | private ReadContext(InputStream input, ObjectMapper mapper) { 17 | this.input = new NoCloseInputStream(input); 18 | this.mapper = mapper; 19 | } 20 | 21 | public static ReadContext getReadContext(InputStream input, ObjectMapper mapper) { 22 | return new ReadContext(input, mapper); 23 | } 24 | 25 | public JsonNode nextValue() throws IOException { 26 | return mapper.readValue(input, JsonNode.class); 27 | } 28 | 29 | public void assertReadable() throws IOException { 30 | try { 31 | if (input.markSupported()) { 32 | input.mark(1); 33 | if (input.read() == -1) { 34 | throw new StreamEndedException(); 35 | } 36 | input.reset(); 37 | } 38 | } catch(SocketException se) { 39 | throw new StreamEndedException(); 40 | } 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | final int prime = 31; 46 | int result = 1; 47 | result = prime * result + input.hashCode(); 48 | result = prime * result + (mapper == null ? 0 : mapper.hashCode()); 49 | return result; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object obj) { 54 | if (this == obj) 55 | return true; 56 | if (obj == null) 57 | return false; 58 | if (getClass() != obj.getClass()) 59 | return false; 60 | ReadContext other = (ReadContext) obj; 61 | if (!input.equals(other.input)) 62 | return false; 63 | if (mapper == null) { 64 | if (other.mapper != null) 65 | return false; 66 | } else if (!mapper.equals(other.mapper)) 67 | return false; 68 | return true; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/NoCloseInputStream.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | @SuppressWarnings({"WeakerAccess", "unused"}) 7 | class NoCloseInputStream extends InputStream { 8 | 9 | private final InputStream input; 10 | private boolean closeAttempted = false; 11 | 12 | public NoCloseInputStream(InputStream input) { 13 | this.input = input; 14 | } 15 | 16 | /** 17 | * {@inheritDoc} 18 | */ 19 | @Override 20 | public int read() throws IOException { 21 | return this.input.read(); 22 | } 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | @Override 28 | public int read(byte[] b) throws IOException { 29 | return this.input.read(b); 30 | } 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | @Override 36 | public int read(byte[] b, int off, int len) throws IOException { 37 | return this.input.read(b, off, len); 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public long skip(long n) throws IOException { 45 | return this.input.skip(n); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | @Override 52 | public int available() throws IOException { 53 | return this.input.available(); 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | @Override 60 | public void close() throws IOException { 61 | closeAttempted = true; 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | */ 67 | @Override 68 | public synchronized void mark(int readLimit) { 69 | this.input.mark(readLimit); 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | @Override 76 | public synchronized void reset() throws IOException { 77 | this.input.reset(); 78 | } 79 | 80 | /** 81 | * {@inheritDoc} 82 | */ 83 | @Override 84 | public boolean markSupported() { 85 | return this.input.markSupported(); 86 | } 87 | 88 | /** 89 | * @return the closeAttempted 90 | */ 91 | public boolean wasCloseAttempted() { 92 | return closeAttempted; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporterIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.test.context.ContextConfiguration; 8 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 9 | 10 | import com.googlecode.jsonrpc4j.spring.service.Service; 11 | import com.googlecode.jsonrpc4j.spring.service.ServiceImpl; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | /** 16 | * This test ensures that {@link com.googlecode.jsonrpc4j.spring.JsonServiceExporter} bean is 17 | * constructed according to Spring Framework configuration. 18 | */ 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | @ContextConfiguration("classpath:serverApplicationContextC.xml") 21 | public class JsonServiceExporterIntegrationTest { 22 | 23 | private static final String BEAN_NAME_AND_URL_PATH = "/UserService.json"; 24 | 25 | @Autowired 26 | private ApplicationContext applicationContext; 27 | 28 | @Test 29 | public void testExportedService() { 30 | assertNotNull(applicationContext); 31 | 32 | // check that the bean was only exported on the configured path. 33 | { 34 | Object bean = applicationContext.getBean(BEAN_NAME_AND_URL_PATH); 35 | assertEquals(JsonServiceExporter.class, bean.getClass()); 36 | 37 | String[] names = applicationContext.getBeanNamesForType(JsonServiceExporter.class); 38 | assertNotNull(names); 39 | assertEquals(1, names.length); 40 | assertEquals(BEAN_NAME_AND_URL_PATH, names[0]); 41 | } 42 | 43 | // check that service classes were also successfully configured in the context. 44 | 45 | { 46 | Service service = applicationContext.getBean(Service.class); 47 | assertTrue(service instanceof ServiceImpl); 48 | 49 | ServiceImpl serviceImpl = applicationContext.getBean(ServiceImpl.class); 50 | assertNotNull(serviceImpl); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/AnnotationsErrorResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * {@link ErrorResolver} that uses annotations. 10 | */ 11 | public enum AnnotationsErrorResolver implements ErrorResolver { 12 | INSTANCE; 13 | 14 | /** 15 | * {@inheritDoc} 16 | */ 17 | @Override 18 | public JsonError resolveError(Throwable thrownException, Method method, List arguments) { 19 | JsonRpcError resolver = getResolverForException(thrownException, method); 20 | if (notFoundResolver(resolver)) { 21 | return null; 22 | } 23 | 24 | String message = hasErrorMessage(resolver) ? resolver.message() : thrownException.getMessage(); 25 | Object data = hasErrorData(resolver) ? resolver.data() : new ErrorData(resolver.exception().getName(), message); 26 | return new JsonError(resolver.code(), message, data); 27 | } 28 | 29 | private JsonRpcError getResolverForException(Throwable thrownException, Method method) { 30 | JsonRpcErrors errors = ReflectionUtil.getAnnotation(method, JsonRpcErrors.class); 31 | if (hasAnnotations(errors)) { 32 | for (JsonRpcError errorDefined : errors.value()) { 33 | if (isExceptionInstanceOfError(thrownException, errorDefined)) { 34 | return errorDefined; 35 | } 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | private boolean notFoundResolver(JsonRpcError resolver) { 42 | return resolver == null; 43 | } 44 | 45 | private boolean hasErrorMessage(JsonRpcError em) { 46 | // noinspection ConstantConditions 47 | return em.message() != null && em.message().trim().length() > 0; 48 | } 49 | 50 | private boolean hasErrorData(JsonRpcError em) { 51 | // noinspection ConstantConditions 52 | return em.data() != null && em.data().trim().length() > 0; 53 | } 54 | 55 | private boolean hasAnnotations(JsonRpcErrors errors) { 56 | return errors != null; 57 | } 58 | 59 | private boolean isExceptionInstanceOfError(Throwable target, JsonRpcError em) { 60 | return em.exception().isInstance(target); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/MultipleInvocationListener.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Collections; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | /** 11 | * {@link com.googlecode.jsonrpc4j.InvocationListener} that supports the use 12 | * of multiple {@link InvocationListener}s called one after another. 13 | * 14 | * @author Andrew Lindesay 15 | */ 16 | 17 | @SuppressWarnings({"unused", "WeakerAccess"}) 18 | public class MultipleInvocationListener implements InvocationListener { 19 | 20 | private final List invocationListeners; 21 | 22 | /** 23 | * Creates with the given {@link InvocationListener}s, 24 | * {@link #addInvocationListener(InvocationListener)} can be called to 25 | * add additional {@link InvocationListener}s. 26 | * 27 | * @param invocationListeners the {@link InvocationListener}s 28 | */ 29 | public MultipleInvocationListener(InvocationListener... invocationListeners) { 30 | this.invocationListeners = new LinkedList<>(); 31 | Collections.addAll(this.invocationListeners, invocationListeners); 32 | } 33 | 34 | /** 35 | * Adds an {@link InvocationListener} to the end of the 36 | * list of invocation listeners. 37 | * 38 | * @param invocationListener the {@link InvocationListener} to add 39 | */ 40 | public void addInvocationListener(InvocationListener invocationListener) { 41 | this.invocationListeners.add(invocationListener); 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | public void willInvoke(Method method, List arguments) { 48 | for (InvocationListener invocationListener : invocationListeners) { 49 | invocationListener.willInvoke(method, arguments); 50 | } 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public void didInvoke(Method method, List arguments, Object result, Throwable t, long duration) { 57 | for (InvocationListener invocationListener : invocationListeners) { 58 | invocationListener.didInvoke(method, arguments, result, t, duration); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /pom.gradle: -------------------------------------------------------------------------------- 1 | allprojects { project -> 2 | publishing { 3 | publications { 4 | jsonrpc4j { 5 | pom { 6 | name = rootProject.name.capitalize() + (project.parent ? " ($shortName)" : '') 7 | description = project.description 8 | url = 'https://github.com/briandilley/jsonrpc4j' 9 | issueManagement { 10 | system = 'GitHub' 11 | url = 'https://github.com/briandilley/jsonrpc4j/issues' 12 | } 13 | ciManagement { 14 | system = 'Github Actions' 15 | url = 'https://github.com/briandilley/jsonrpc4j/actions' 16 | } 17 | inceptionYear = '2013' 18 | developers { 19 | developer { 20 | id = 'briandilley' 21 | name = 'Brian Dilley' 22 | email = 'brian.dilley@gmail.com.com' 23 | url = 'https://github.com/briandilley' 24 | timezone = 'America/Los_Angeles' 25 | } 26 | } 27 | licenses { 28 | license { 29 | name = 'The MIT License (MIT)' 30 | url = 'https://github.com/briandilley/jsonrpc4j/blob/develop/LICENSE' 31 | distribution = 'repo' 32 | comments = 'A business-friendly OSS license' 33 | } 34 | } 35 | scm { 36 | connection = 'scm:git:https://github.com/briandilley/jsonrpc4j.git' 37 | developerConnection = 'git@github.com:briandilley/jsonrpc4j.git' 38 | url = 'https://github.com/briandilley/jsonrpc4j' 39 | } 40 | distributionManagement { 41 | downloadUrl = 'https://github.com/briandilley/jsonrpc4j/releases' 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/LocalThreadServer.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.JsonRpcClient; 5 | import com.googlecode.jsonrpc4j.JsonRpcServer; 6 | import com.googlecode.jsonrpc4j.ProxyUtil; 7 | import com.googlecode.jsonrpc4j.RequestInterceptor; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.io.PipedInputStream; 15 | import java.io.PipedOutputStream; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | public class LocalThreadServer extends Thread implements AutoCloseable { 19 | private static final Logger logger = LoggerFactory.getLogger(LocalThreadServer.class); 20 | 21 | private final InputStream serverInput = new PipedInputStream(); 22 | private final OutputStream serverOutput = new PipedOutputStream(); 23 | private final AtomicBoolean isAlive = new AtomicBoolean(true); 24 | 25 | private final JsonRpcServer jsonRpcServer; 26 | private final Class remoteInterface; 27 | private final Object service; 28 | 29 | public LocalThreadServer(Object service, final Class remoteInterface) { 30 | this.remoteInterface = remoteInterface; 31 | this.service = service; 32 | jsonRpcServer = new JsonRpcServer(new ObjectMapper(), service, remoteInterface); 33 | start(); 34 | } 35 | 36 | public void setRequestInterceptor(RequestInterceptor interceptor) { 37 | jsonRpcServer.setRequestInterceptor(interceptor); 38 | } 39 | 40 | public T client(Class clazz) throws IOException { 41 | InputStream clientInput = new PipedInputStream((PipedOutputStream) serverOutput); 42 | OutputStream clientOutput = new PipedOutputStream((PipedInputStream) serverInput); 43 | return ProxyUtil.createClientProxy(ClassLoader.getSystemClassLoader(), clazz, new JsonRpcClient(), clientInput, clientOutput); 44 | } 45 | 46 | @Override 47 | public void run() { 48 | logger.debug("started local thread server for interface {} and handler {}", remoteInterface, service); 49 | while (isAlive.get()) { 50 | try { 51 | final int available = serverInput.available(); 52 | if (available > 0) { 53 | jsonRpcServer.handleRequest(serverInput, serverOutput); 54 | } 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | } 61 | 62 | @Override 63 | public void close() throws Exception { 64 | isAlive.set(false); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/BaseRestTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.JsonRpcHttpClient; 5 | import com.googlecode.jsonrpc4j.spring.rest.JsonRpcRestClient; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | import org.junit.rules.ExpectedException; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.net.MalformedURLException; 13 | import java.net.URL; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | public abstract class BaseRestTest { 18 | 19 | @Rule 20 | final public ExpectedException expectedEx = ExpectedException.none(); 21 | private JettyServer jettyServer; 22 | 23 | @Before 24 | public void setup() throws Exception { 25 | this.jettyServer = createServer(); 26 | } 27 | 28 | private JettyServer createServer() throws Exception { 29 | Class service = service(); 30 | if (service == null) return null; 31 | JettyServer jettyServer = new JettyServer(service); 32 | jettyServer.startup(); 33 | return jettyServer; 34 | } 35 | 36 | protected abstract Class service(); 37 | 38 | protected JsonRpcRestClient getClient() throws MalformedURLException { 39 | return getClient(JettyServer.SERVLET); 40 | } 41 | 42 | protected JsonRpcRestClient getClient(final String servlet) throws MalformedURLException { 43 | return new JsonRpcRestClient(new URL(jettyServer.getCustomServerUrlString(servlet))); 44 | } 45 | 46 | protected JsonRpcRestClient getClient(final String servlet, RestTemplate restTemplate) throws MalformedURLException { 47 | return new JsonRpcRestClient(new URL(jettyServer.getCustomServerUrlString(servlet)), restTemplate); 48 | } 49 | 50 | protected JsonRpcHttpClient getHttpClient(boolean gzipRequests, boolean acceptGzipResponses) throws MalformedURLException { 51 | Map header = new HashMap<>(); 52 | return new JsonRpcHttpClient(new ObjectMapper(), new URL(jettyServer.getCustomServerUrlString(JettyServer.SERVLET)), header, gzipRequests, acceptGzipResponses); 53 | } 54 | 55 | protected JsonRpcHttpClient getHttpClient(final String servlet, boolean gzipRequests, boolean acceptGzipResponses) throws MalformedURLException { 56 | Map header = new HashMap<>(); 57 | return new JsonRpcHttpClient(new ObjectMapper(), new URL(jettyServer.getCustomServerUrlString(servlet)), header, gzipRequests, acceptGzipResponses); 58 | } 59 | 60 | @After 61 | public void teardown() throws Exception { 62 | jettyServer.stop(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/HttpCodeTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcClientException; 4 | import com.googlecode.jsonrpc4j.ProxyUtil; 5 | import com.googlecode.jsonrpc4j.spring.rest.JsonRpcRestClient; 6 | import com.googlecode.jsonrpc4j.util.BaseRestTest; 7 | import com.googlecode.jsonrpc4j.util.FakeServiceInterface; 8 | import com.googlecode.jsonrpc4j.util.FakeServiceInterfaceImpl; 9 | import com.googlecode.jsonrpc4j.util.JettyServer; 10 | import org.junit.Test; 11 | import org.springframework.web.client.DefaultResponseErrorHandler; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import java.net.MalformedURLException; 15 | 16 | import static org.hamcrest.CoreMatchers.anyOf; 17 | import static org.hamcrest.CoreMatchers.endsWith; 18 | import static org.hamcrest.CoreMatchers.equalTo; 19 | import static org.hamcrest.CoreMatchers.startsWith; 20 | 21 | public class HttpCodeTest extends BaseRestTest { 22 | 23 | @Test 24 | public void http405OnInvalidUrl() throws MalformedURLException { 25 | expectedEx.expectMessage( 26 | anyOf( 27 | equalTo("405 HTTP method POST is not supported by this URL"), 28 | equalTo("404 Not Found"), 29 | equalTo("HTTP method POST is not supported by this URL"), 30 | startsWith("Server returned HTTP response code: 405 for URL: http://127.0.0.1"), 31 | endsWith("Method Not Allowed") 32 | ) 33 | ); 34 | expectedEx.expect(JsonRpcClientException.class); 35 | FakeServiceInterface service = ProxyUtil.createClientProxy(FakeServiceInterface.class, getClient("error")); 36 | service.doSomething(); 37 | } 38 | 39 | @Test 40 | public void http200() throws MalformedURLException { 41 | FakeServiceInterface service = ProxyUtil.createClientProxy(FakeServiceInterface.class, getClient()); 42 | service.doSomething(); 43 | } 44 | 45 | @Test 46 | public void httpCustomStatus() throws MalformedURLException { 47 | expectedEx.expectMessage(equalTo("Server Error")); 48 | expectedEx.expect(JsonRpcClientException.class); 49 | 50 | RestTemplate restTemplate = new RestTemplate(); 51 | 52 | JsonRpcRestClient client = getClient(JettyServer.SERVLET, restTemplate); 53 | 54 | // Overwrite error handler for error check. 55 | restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); 56 | 57 | FakeServiceInterface service = ProxyUtil.createClientProxy(FakeServiceInterface.class, client); 58 | service.throwSomeException("function error"); 59 | } 60 | 61 | @Override 62 | protected Class service() { 63 | return FakeServiceInterfaceImpl.class; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/IJsonRpcClient.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.lang.reflect.Type; 4 | import java.util.Map; 5 | 6 | public interface IJsonRpcClient { 7 | 8 | /** 9 | * Invokes the given method with the given argument. 10 | * 11 | * @param methodName the name of the method to invoke 12 | * @param argument the argument to the method 13 | * @throws Throwable on error 14 | */ 15 | void invoke(String methodName, Object argument) throws Throwable; 16 | 17 | /** 18 | * Invokes the given method with the given arguments and returns 19 | * an object of the given type, or null if void. 20 | * 21 | * @param methodName the name of the method to invoke 22 | * @param argument the argument to the method 23 | * @param returnType the return type 24 | * @return the return value 25 | * @throws Throwable on error 26 | */ 27 | Object invoke(String methodName, Object argument, Type returnType) throws Throwable; 28 | 29 | /** 30 | * Invokes the given method with the given arguments and returns 31 | * an object of the given type, or null if void. 32 | * 33 | * @param methodName the name of the method to invoke 34 | * @param argument the argument to the method 35 | * @param returnType the return type 36 | * @param extraHeaders extra headers to add to the request 37 | * @return the return value 38 | * @throws Throwable on error 39 | */ 40 | Object invoke(String methodName, Object argument, Type returnType, Map extraHeaders) throws Throwable; 41 | 42 | /** 43 | * Invokes the given method with the given arguments and returns 44 | * an object of the given type, or null if void. 45 | * 46 | * @param methodName the name of the method to invoke 47 | * @param argument the argument to the method 48 | * @param clazz the return type 49 | * @param the return type 50 | * @return the return value 51 | * @throws Throwable on error 52 | */ 53 | T invoke(String methodName, Object argument, Class clazz) throws Throwable; 54 | 55 | /** 56 | * Invokes the given method with the given arguments and returns 57 | * an object of the given type, or null if void. 58 | * 59 | * @param methodName the name of the method to invoke 60 | * @param argument the argument to the method 61 | * @param clazz the return type 62 | * @param extraHeaders extra headers to add to the request 63 | * @param the return type 64 | * @return the return value 65 | * @throws Throwable on error 66 | */ 67 | T invoke(String methodName, Object argument, Class clazz, Map extraHeaders) throws Throwable; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/spring/JsonRpcPathServerIntegrationTestB.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.spring.serviceb.NoopTemperatureImpl; 4 | import com.googlecode.jsonrpc4j.spring.serviceb.Temperature; 5 | import com.googlecode.jsonrpc4j.spring.serviceb.TemperatureImpl; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 12 | 13 | import java.util.Arrays; 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertNotNull; 20 | import static org.junit.Assert.assertSame; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | /** 24 | *

This test replaces {@link JsonRpcPathServerIntegrationTest} and uses the new class 25 | * {@link AutoJsonRpcServiceImplExporter} which is designed to vend specific annotated 26 | * implementations.

27 | */ 28 | 29 | @RunWith(SpringJUnit4ClassRunner.class) 30 | @ContextConfiguration("classpath:serverApplicationContextB.xml") 31 | public class JsonRpcPathServerIntegrationTestB { 32 | 33 | @Autowired 34 | private ApplicationContext applicationContext; 35 | 36 | @Test 37 | public void testExportedService() { 38 | assertNotNull(applicationContext); 39 | 40 | // check that the service was vended on both paths that were configured. 41 | 42 | { 43 | Object bean = applicationContext.getBean("/api/temperature"); 44 | assertSame(JsonServiceExporter.class, bean.getClass()); 45 | } 46 | 47 | { 48 | Object bean = applicationContext.getBean("/api-web/temperature"); 49 | assertSame(JsonServiceExporter.class, bean.getClass()); 50 | } 51 | 52 | // check that the bean was only exported on the two paths provided. 53 | 54 | { 55 | String[] names = applicationContext.getBeanNamesForType(JsonServiceExporter.class); 56 | Arrays.sort(names); 57 | assertSame(2, names.length); 58 | assertEquals("/api-web/temperature", names[0]); 59 | assertEquals("/api/temperature", names[1]); 60 | } 61 | 62 | // check that the no-op was also successfully configured in the context. 63 | 64 | { 65 | Map beans = applicationContext.getBeansOfType(Temperature.class); 66 | assertSame(2, beans.size()); 67 | Set> beanClasses = new HashSet<>(); 68 | 69 | for (Temperature temperature : beans.values()) { 70 | beanClasses.add(temperature.getClass()); 71 | } 72 | 73 | assertTrue(beanClasses.contains(NoopTemperatureImpl.class)); 74 | assertTrue(beanClasses.contains(TemperatureImpl.class)); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * Interceptor can work with parsed full JSON RPC {@link JsonNode} and with parsed target, method and arguments list. 10 | * In case you want to work with raw input you can use filters. 11 | * In case you want to work with your parsed objects you can use aspect on your exported service. 12 | *

13 | * Interceptors could throw {@link RuntimeException}, in this case JSON RPC standard error will be shown, except preHandleJson. 14 | * In case preHandleJson throws exception, it will generate HTTP code 400 "Bad Request" with empty body. So be careful. 15 | * @author pavyazankin 16 | * @since 21/09/2017 17 | */ 18 | public interface JsonRpcInterceptor { 19 | 20 | /** 21 | * Method to intercept raw JSON RPC json objects. Don't throw exceptions here. 22 | * In case preHandleJson throws exception, it will be HTTP code 400 "Bad Request" with empty body. So be careful. 23 | * @param json raw JSON RPC request json 24 | */ 25 | void preHandleJson(JsonNode json); 26 | 27 | /** 28 | * If exception will be thrown in this method, standard JSON RPC error will be generated. 29 | *

Example 30 | *

31 |      * {
32 |      *      "jsonrpc":"2.0",
33 |      *      "id":0,
34 |      *      "error":{
35 |      *          "code":-32001,
36 |      *          "message":"123",
37 |      *          "data":{
38 |      *              "exceptionTypeName":"java.lang.RuntimeException",
39 |      *              "message":"123"
40 |      *          }
41 |      *      }
42 |      * }
43 |      * 
44 | *

45 | * For changing exception handling custom {@link ErrorResolver} could be generated. 46 | *

47 | * @param target target service 48 | * @param method target method 49 | * @param params list of params as {@link JsonNode}s 50 | */ 51 | void preHandle(Object target, Method method, List params); 52 | 53 | /** 54 | * If exception will be thrown in this method, standard JSON RPC error will be generated. Example in preHandle 55 | * Even if target method retruns without exception. 56 | * @param target target service 57 | * @param method target method 58 | * @param params list of params as {@link JsonNode}s 59 | * @param result returned by target service 60 | */ 61 | void postHandle(Object target, Method method, List params, JsonNode result); 62 | 63 | /** 64 | * If exception will be thrown in this method, standard JSON RPC error will be generated. Example in preHandle 65 | * Even if target method retruns without exception. 66 | * @param json raw JSON RPC response json 67 | */ 68 | void postHandleJson(JsonNode json); 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/JettyServer.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import com.googlecode.jsonrpc4j.AnnotationsErrorResolver; 4 | import com.googlecode.jsonrpc4j.JsonRpcServer; 5 | import org.eclipse.jetty.server.Server; 6 | import org.eclipse.jetty.servlet.ServletContextHandler; 7 | import org.eclipse.jetty.servlet.ServletHolder; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.util.Random; 16 | 17 | import static com.googlecode.jsonrpc4j.util.Util.DEFAULT_LOCAL_HOSTNAME; 18 | 19 | @SuppressWarnings("WeakerAccess") 20 | public class JettyServer implements AutoCloseable { 21 | 22 | public static final String SERVLET = "someSunnyServlet"; 23 | private static final String PROTOCOL = "http"; 24 | 25 | private final Class service; 26 | private Server jetty; 27 | private int port; 28 | 29 | JettyServer(Class service) { 30 | this.service = service; 31 | } 32 | 33 | public String getCustomServerUrlString(final String servletName) { 34 | return PROTOCOL + "://" + DEFAULT_LOCAL_HOSTNAME + ":" + port + "/" + servletName; 35 | } 36 | 37 | public void startup() throws Exception { 38 | port = 10000 + new Random().nextInt(30000); 39 | jetty = new Server(port); 40 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); 41 | context.setContextPath("/"); 42 | jetty.setHandler(context); 43 | ServletHolder servlet = context.addServlet(JsonRpcTestServlet.class, "/" + SERVLET); 44 | servlet.setInitParameter("class", service.getCanonicalName()); 45 | jetty.start(); 46 | } 47 | 48 | @Override 49 | public void close() throws Exception { 50 | this.stop(); 51 | } 52 | 53 | public void stop() throws Exception { 54 | jetty.stop(); 55 | } 56 | 57 | public static class JsonRpcTestServlet extends HttpServlet { 58 | 59 | static final long serialVersionUID = 1L; 60 | private transient JsonRpcServer jsonRpcServer; 61 | 62 | @Override 63 | public void init() { 64 | try { 65 | final Class aClass = Class.forName(getInitParameter("class")); 66 | final Object instance = aClass.getConstructor().newInstance(); 67 | jsonRpcServer = new JsonRpcServer(instance); 68 | jsonRpcServer.setErrorResolver(AnnotationsErrorResolver.INSTANCE); 69 | } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) { 70 | e.printStackTrace(); 71 | } 72 | 73 | } 74 | 75 | @Override 76 | protected void doPost(HttpServletRequest request, HttpServletResponse response) 77 | throws ServletException, IOException { 78 | jsonRpcServer.handle(request, response); 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/DefaultHttpStatusCodeProvider.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import java.net.HttpURLConnection; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.BULK_ERROR; 9 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.CUSTOM_SERVER_ERROR_LOWER; 10 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.CUSTOM_SERVER_ERROR_UPPER; 11 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.ERROR_NOT_HANDLED; 12 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.INTERNAL_ERROR; 13 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.INVALID_REQUEST; 14 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.METHOD_NOT_FOUND; 15 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.METHOD_PARAMS_INVALID; 16 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.PARSE_ERROR; 17 | 18 | /** 19 | * This default implementation of a {@link HttpStatusCodeProvider} follows the rules defined in the 20 | * JSON-RPC over HTTP document. 21 | */ 22 | @SuppressWarnings("WeakerAccess") 23 | public enum DefaultHttpStatusCodeProvider implements HttpStatusCodeProvider { 24 | INSTANCE; 25 | 26 | final Map httpStatus2JsonError = new HashMap(); 27 | 28 | private DefaultHttpStatusCodeProvider() { 29 | httpStatus2JsonError.put(HttpURLConnection.HTTP_INTERNAL_ERROR, INTERNAL_ERROR); 30 | httpStatus2JsonError.put(HttpURLConnection.HTTP_NOT_FOUND, METHOD_NOT_FOUND); 31 | httpStatus2JsonError.put(HttpURLConnection.HTTP_BAD_REQUEST, PARSE_ERROR); 32 | } 33 | 34 | 35 | @Override 36 | public int getHttpStatusCode(int resultCode) { 37 | if (resultCode == 0) return HttpURLConnection.HTTP_OK; // Toha: pure java constants 38 | 39 | if (isErrorCode(resultCode)) { 40 | return HttpURLConnection.HTTP_INTERNAL_ERROR; 41 | } else if (resultCode == INVALID_REQUEST.code || resultCode == PARSE_ERROR.code) { 42 | return HttpURLConnection.HTTP_BAD_REQUEST; 43 | } else if (resultCode == METHOD_NOT_FOUND.code) { 44 | return HttpURLConnection.HTTP_NOT_FOUND; 45 | } 46 | 47 | return HttpURLConnection.HTTP_OK; 48 | } 49 | 50 | private boolean isErrorCode(int result) { 51 | for (ErrorResolver.JsonError error : Arrays.asList(INTERNAL_ERROR, METHOD_PARAMS_INVALID, ERROR_NOT_HANDLED, BULK_ERROR)) { 52 | if (error.code == result) return true; 53 | } 54 | return CUSTOM_SERVER_ERROR_UPPER >= result && result >= CUSTOM_SERVER_ERROR_LOWER; 55 | } 56 | 57 | @Override 58 | public Integer getJsonRpcCode(int httpStatusCode) { 59 | return httpStatus2JsonError.containsKey(httpStatusCode) 60 | ? httpStatus2JsonError.get(httpStatusCode).getCode() 61 | : null; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/ErrorResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * Resolves {@link Throwable}s thrown by JSON-RPC services. 10 | */ 11 | public interface ErrorResolver { 12 | 13 | /** 14 | * Resolves the error using the {@link Throwable} that 15 | * was thrown by the given {@link Method} when passed 16 | * the given {@code arguments}. If the error can not 17 | * be resolved then null is returned. 18 | * 19 | * @param t the {@link Throwable} 20 | * @param method the {@link Method} that threw the {@link Throwable} 21 | * @param arguments the {@code arguments} that were passed to the {@link Method} 22 | * @return the {@link JsonError} or null if it couldn't be resolved 23 | */ 24 | JsonError resolveError(Throwable t, Method method, List arguments); 25 | 26 | /** 27 | * A JSON error. 28 | */ 29 | @SuppressWarnings({"WeakerAccess", "unused"}) 30 | class JsonError { 31 | 32 | public static final JsonError OK = new JsonError(0, "ok", null); 33 | public static final JsonError PARSE_ERROR = new JsonError(-32700, "JSON parse error", null); 34 | public static final JsonError INVALID_REQUEST = new JsonError(-32600, "invalid request", null); 35 | public static final JsonError METHOD_NOT_FOUND = new JsonError(-32601, "method not found", null); 36 | public static final JsonError METHOD_PARAMS_INVALID = new JsonError(-32602, "method parameters invalid", null); 37 | public static final JsonError INTERNAL_ERROR = new JsonError(-32603, "internal error", null); 38 | public static final JsonError ERROR_NOT_HANDLED = new JsonError(-32001, "error not handled", null); 39 | public static final JsonError BULK_ERROR = new JsonError(-32002, "bulk error", null); 40 | 41 | public static final int CUSTOM_SERVER_ERROR_UPPER = -32000; 42 | public static final int CUSTOM_SERVER_ERROR_LOWER = -32099; 43 | 44 | public final int code; 45 | public final String message; 46 | public final Object data; 47 | 48 | /** 49 | * Creates the error. 50 | * 51 | * @param code the code 52 | * @param message the message 53 | * @param data the data 54 | */ 55 | public JsonError(int code, String message, Object data) { 56 | this.code = code; 57 | this.message = message; 58 | this.data = data; 59 | } 60 | 61 | /** 62 | * @return the code 63 | */ 64 | int getCode() { 65 | return code; 66 | } 67 | 68 | /** 69 | * @return the message 70 | */ 71 | String getMessage() { 72 | return message; 73 | } 74 | 75 | /** 76 | * @return the data 77 | */ 78 | Object getData() { 79 | return data; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "JsonError{" + "code=" + code + 85 | ", message='" + message + '\'' + 86 | ", data=" + data + 87 | '}'; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/CompositeJsonStreamServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.StreamServer; 4 | import org.springframework.beans.factory.DisposableBean; 5 | 6 | import javax.net.ServerSocketFactory; 7 | import java.net.InetAddress; 8 | import java.net.ServerSocket; 9 | 10 | import static com.googlecode.jsonrpc4j.Util.DEFAULT_HOSTNAME; 11 | 12 | /** 13 | * A stream service exporter that exports multiple 14 | * services as a single service. 15 | */ 16 | @SuppressWarnings("unused") 17 | public class CompositeJsonStreamServiceExporter extends AbstractCompositeJsonServiceExporter implements DisposableBean { 18 | 19 | private static final int DEFAULT_MAX_THREADS = 50; 20 | private static final int DEFAULT_PORT = 10420; 21 | private static final int DEFAULT_BACKLOG = 0; 22 | private static final int DEFAULT_MAX_CLIENT_ERRORS = 5; 23 | 24 | private ServerSocketFactory serverSocketFactory; 25 | private int maxThreads = DEFAULT_MAX_THREADS; 26 | private int port = DEFAULT_PORT; 27 | private int backlog = DEFAULT_BACKLOG; 28 | private int maxClientErrors = DEFAULT_MAX_CLIENT_ERRORS; 29 | private String hostName = DEFAULT_HOSTNAME; 30 | 31 | private StreamServer streamServer; 32 | 33 | /** 34 | * {@inheritDoc} 35 | */ 36 | @Override 37 | protected void exportService() throws Exception { 38 | if (streamServer == null) { 39 | if (serverSocketFactory == null) { 40 | serverSocketFactory = ServerSocketFactory.getDefault(); 41 | } 42 | ServerSocket serverSocket = serverSocketFactory.createServerSocket(port, backlog, InetAddress.getByName(hostName)); 43 | streamServer = new StreamServer(getJsonRpcServer(), maxThreads, serverSocket); 44 | streamServer.setMaxClientErrors(maxClientErrors); 45 | } 46 | streamServer.start(); 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | public void destroy() throws Exception { 53 | streamServer.stop(); 54 | } 55 | 56 | /** 57 | * @param serverSocketFactory the serverSocketFactory to set 58 | */ 59 | public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) { 60 | this.serverSocketFactory = serverSocketFactory; 61 | } 62 | 63 | /** 64 | * @param maxThreads the maxThreads to set 65 | */ 66 | public void setMaxThreads(int maxThreads) { 67 | this.maxThreads = maxThreads; 68 | } 69 | 70 | /** 71 | * @param port the port to set 72 | */ 73 | public void setPort(int port) { 74 | this.port = port; 75 | } 76 | 77 | /** 78 | * @param backlog the backlog to set 79 | */ 80 | public void setBacklog(int backlog) { 81 | this.backlog = backlog; 82 | } 83 | 84 | /** 85 | * @param hostName the hostName to set 86 | */ 87 | public void setHostName(String hostName) { 88 | this.hostName = hostName; 89 | } 90 | 91 | /** 92 | * @param streamServer the streamServer to set 93 | */ 94 | public void setStreamServer(StreamServer streamServer) { 95 | this.streamServer = streamServer; 96 | } 97 | 98 | /** 99 | * @param maxClientErrors the maxClientErrors to set 100 | */ 101 | public void setMaxClientErrors(int maxClientErrors) { 102 | this.maxClientErrors = maxClientErrors; 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/JsonRpcServerAnnotateMethodTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.googlecode.jsonrpc4j.JsonRpcBasicServer; 5 | import com.googlecode.jsonrpc4j.JsonRpcMethod; 6 | import org.easymock.EasyMock; 7 | import org.easymock.EasyMockRunner; 8 | import org.easymock.Mock; 9 | import org.easymock.MockType; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | 14 | import java.io.ByteArrayOutputStream; 15 | import java.io.IOException; 16 | 17 | import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.RESULT; 18 | import static com.googlecode.jsonrpc4j.util.Util.decodeAnswer; 19 | import static com.googlecode.jsonrpc4j.util.Util.mapper; 20 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParamsStream; 21 | import static com.googlecode.jsonrpc4j.util.Util.messageWithMapParamsStream; 22 | import static com.googlecode.jsonrpc4j.util.Util.param1; 23 | import static com.googlecode.jsonrpc4j.util.Util.param2; 24 | import static org.junit.Assert.assertEquals; 25 | 26 | @RunWith(EasyMockRunner.class) 27 | public class JsonRpcServerAnnotateMethodTest { 28 | @Mock(type = MockType.NICE) 29 | private ServiceInterfaceWithCustomMethodNameAnnotation mockService; 30 | 31 | private ByteArrayOutputStream byteArrayOutputStream; 32 | private JsonRpcBasicServer jsonRpcServerAnnotatedMethod; 33 | 34 | @Before 35 | public void setup() { 36 | byteArrayOutputStream = new ByteArrayOutputStream(); 37 | jsonRpcServerAnnotatedMethod = new JsonRpcBasicServer(mapper, mockService, ServiceInterfaceWithCustomMethodNameAnnotation.class); 38 | } 39 | 40 | @Test 41 | public void callMethodWithCustomMethodNameTest() throws Exception { 42 | EasyMock.expect(mockService.customMethod()).andReturn(param1); 43 | EasyMock.replay(mockService); 44 | jsonRpcServerAnnotatedMethod.handleRequest(messageWithMapParamsStream("Test.custom"), byteArrayOutputStream); 45 | assertEquals(param1, result().textValue()); 46 | } 47 | 48 | private JsonNode result() throws IOException { 49 | return decodeAnswer(byteArrayOutputStream).get(RESULT); 50 | } 51 | 52 | @Test 53 | public void callMethodWithoutCustomMethodNameTest() throws Exception { 54 | EasyMock.expect(mockService.customMethod()).andReturn(param1); 55 | EasyMock.replay(mockService); 56 | jsonRpcServerAnnotatedMethod.handleRequest(messageWithListParamsStream(1, "customMethod"), byteArrayOutputStream); 57 | assertEquals(param1, result().textValue()); 58 | } 59 | 60 | @Test 61 | public void callMethodWithCustomMethodNameAndParamTest() throws Exception { 62 | EasyMock.expect(mockService.customMethod2(param1)).andReturn(param2); 63 | EasyMock.replay(mockService); 64 | jsonRpcServerAnnotatedMethod.handleRequest(messageWithListParamsStream(1, "Test.custom2", param1), byteArrayOutputStream); 65 | assertEquals(param2, result().textValue()); 66 | } 67 | 68 | @Test 69 | public void callMethodWithoutCustomMethodNameAndParamTest() throws Exception { 70 | EasyMock.expect(mockService.customMethod2(param1)).andReturn(param2); 71 | EasyMock.replay(mockService); 72 | jsonRpcServerAnnotatedMethod.handleRequest(messageWithListParamsStream(1, "customMethod2", param1), byteArrayOutputStream); 73 | assertEquals(param2, result().textValue()); 74 | } 75 | 76 | public interface ServiceInterfaceWithCustomMethodNameAnnotation { 77 | @JsonRpcMethod("Test.custom") 78 | String customMethod(); 79 | 80 | @JsonRpcMethod("Test.custom2") 81 | String customMethod2(String stringParam1); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/JsonStreamServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.StreamServer; 4 | import org.springframework.beans.factory.DisposableBean; 5 | 6 | import javax.net.ServerSocketFactory; 7 | import java.net.InetAddress; 8 | import java.net.ServerSocket; 9 | 10 | import static com.googlecode.jsonrpc4j.Util.DEFAULT_HOSTNAME; 11 | 12 | 13 | /** 14 | * Exports user defined services as streaming server, which provides JSON-RPC over sockets. 15 | */ 16 | @SuppressWarnings("unused") 17 | public class JsonStreamServiceExporter extends AbstractJsonServiceExporter implements DisposableBean { 18 | 19 | private static final int DEFAULT_MAX_THREADS = 50; 20 | private static final int DEFAULT_PORT = 10420; 21 | private static final int DEFAULT_BACKLOG = 0; 22 | private static final int DEFAULT_MAX_CLIENT_ERRORS = 5; 23 | 24 | private ServerSocketFactory serverSocketFactory; 25 | private int maxThreads = DEFAULT_MAX_THREADS; 26 | private int port = DEFAULT_PORT; 27 | private int backlog = DEFAULT_BACKLOG; 28 | private int maxClientErrors = DEFAULT_MAX_CLIENT_ERRORS; 29 | private String hostName = DEFAULT_HOSTNAME; 30 | 31 | private StreamServer streamServer; 32 | 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | protected void exportService() throws Exception { 39 | 40 | if (streamServer == null) { 41 | if (serverSocketFactory == null) { 42 | serverSocketFactory = ServerSocketFactory.getDefault(); 43 | } 44 | ServerSocket serverSocket = 45 | serverSocketFactory.createServerSocket(port, backlog, InetAddress.getByName(hostName)); 46 | streamServer = new StreamServer(getJsonRpcServer(), maxThreads, serverSocket); 47 | streamServer.setMaxClientErrors(maxClientErrors); 48 | } 49 | streamServer.start(); 50 | } 51 | 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public void destroy() throws Exception { 57 | streamServer.stop(); 58 | } 59 | 60 | 61 | /** 62 | * @param serverSocketFactory the serverSocketFactory to set 63 | */ 64 | public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) { 65 | this.serverSocketFactory = serverSocketFactory; 66 | } 67 | 68 | 69 | /** 70 | * @param maxThreads the maxThreads to set 71 | */ 72 | public void setMaxThreads(int maxThreads) { 73 | this.maxThreads = maxThreads; 74 | } 75 | 76 | 77 | /** 78 | * @param port the port to set 79 | */ 80 | public void setPort(int port) { 81 | this.port = port; 82 | } 83 | 84 | 85 | /** 86 | * @param backlog the backlog to set 87 | */ 88 | public void setBacklog(int backlog) { 89 | this.backlog = backlog; 90 | } 91 | 92 | 93 | /** 94 | * @param hostName the hostName to set 95 | */ 96 | public void setHostName(String hostName) { 97 | this.hostName = hostName; 98 | } 99 | 100 | 101 | /** 102 | * @param streamServer the streamServer to set 103 | */ 104 | public void setStreamServer(StreamServer streamServer) { 105 | this.streamServer = streamServer; 106 | } 107 | 108 | 109 | /** 110 | * @param maxClientErrors the maxClientErrors to set 111 | */ 112 | public void setMaxClientErrors(int maxClientErrors) { 113 | this.maxClientErrors = maxClientErrors; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/MultiServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.SerializerProvider; 7 | import com.fasterxml.jackson.databind.module.SimpleModule; 8 | import com.googlecode.jsonrpc4j.JsonRpcMultiServer; 9 | import com.googlecode.jsonrpc4j.JsonRpcParam; 10 | import com.googlecode.jsonrpc4j.JsonRpcServer; 11 | import org.easymock.EasyMock; 12 | import org.easymock.EasyMockRunner; 13 | import org.easymock.Mock; 14 | import org.easymock.MockType; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | 19 | import java.beans.Transient; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | 23 | import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.RESULT; 24 | import static com.googlecode.jsonrpc4j.util.Util.decodeAnswer; 25 | import static com.googlecode.jsonrpc4j.util.Util.messageWithMapParamsStream; 26 | import static com.googlecode.jsonrpc4j.util.Util.param1; 27 | import static com.googlecode.jsonrpc4j.util.Util.param2; 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.fail; 30 | 31 | @RunWith(EasyMockRunner.class) 32 | public class MultiServiceTest { 33 | 34 | private static final String serviceName = "Test"; 35 | @Mock(type = MockType.NICE) 36 | private ServiceInterfaceWithParamNameAnnotation mockService; 37 | private JsonRpcMultiServer multiServer; 38 | private ByteArrayOutputStream byteArrayOutputStream; 39 | 40 | @Before 41 | public void setup() { 42 | multiServer = new JsonRpcMultiServer(); 43 | multiServer.addService(serviceName, mockService, ServiceInterfaceWithParamNameAnnotation.class); 44 | byteArrayOutputStream = new ByteArrayOutputStream(); 45 | } 46 | 47 | @Test 48 | public void callMethodExactNumberOfParametersNamed() throws Exception { 49 | EasyMock.expect(mockService.testMethod(param2)).andReturn("success"); 50 | EasyMock.replay(mockService); 51 | 52 | multiServer.handleRequest(messageWithMapParamsStream(serviceName + JsonRpcMultiServer.DEFAULT_SEPARATOR + "testMethod", param1, param2), byteArrayOutputStream); 53 | 54 | assertEquals("success", decodeAnswer(byteArrayOutputStream).get(RESULT).textValue()); 55 | } 56 | 57 | /** Test that verifies the custom ObjectMapper is actually used for serialization 58 | * by adding a custom String serializer that converts to uppercase. */ 59 | @Test 60 | public void testCustomObjectMapperConstructor() throws Exception { 61 | 62 | ObjectMapper customMapper = new ObjectMapper(); 63 | SimpleModule module = new SimpleModule(); 64 | 65 | module.addSerializer(String.class, new JsonSerializer() { 66 | @Override 67 | public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 68 | gen.writeString(value.toUpperCase()); 69 | } 70 | }); 71 | customMapper.registerModule(module); 72 | 73 | JsonRpcMultiServer customServer = new JsonRpcMultiServer(customMapper); 74 | customServer.addService(serviceName, mockService, ServiceInterfaceWithParamNameAnnotation.class); 75 | 76 | EasyMock.expect(mockService.testMethod(param2)).andReturn("custom-result-lowercase"); 77 | EasyMock.replay(mockService); 78 | 79 | ByteArrayOutputStream customOutput = new ByteArrayOutputStream(); 80 | customServer.handleRequest(messageWithMapParamsStream(serviceName + JsonRpcMultiServer.DEFAULT_SEPARATOR + "testMethod", param1, param2), customOutput); 81 | 82 | // If the custom ObjectMapper is used, the result should be in uppercase 83 | String result = decodeAnswer(customOutput).get(RESULT).textValue(); 84 | assertEquals("CUSTOM-RESULT-LOWERCASE", result); 85 | } 86 | 87 | public interface ServiceInterfaceWithParamNameAnnotation { 88 | String testMethod(@JsonRpcParam("param1") String param1); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/DefaultHttpStatusCodeProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcServer; 4 | import org.easymock.EasyMockRunner; 5 | import org.easymock.Mock; 6 | import org.easymock.MockType; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.http.HttpMethod; 12 | import org.springframework.mock.web.MockHttpServletRequest; 13 | import org.springframework.mock.web.MockHttpServletResponse; 14 | 15 | import java.io.InputStream; 16 | 17 | import static com.googlecode.jsonrpc4j.util.Util.convertInputStreamToByteArray; 18 | import static com.googlecode.jsonrpc4j.util.Util.intParam1; 19 | import static com.googlecode.jsonrpc4j.util.Util.intParam2; 20 | import static com.googlecode.jsonrpc4j.util.Util.invalidJsonRpcRequestStream; 21 | import static com.googlecode.jsonrpc4j.util.Util.invalidJsonStream; 22 | import static com.googlecode.jsonrpc4j.util.Util.mapper; 23 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParams; 24 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParamsStream; 25 | import static com.googlecode.jsonrpc4j.util.Util.multiMessageOfStream; 26 | import static com.googlecode.jsonrpc4j.util.Util.param1; 27 | import static com.googlecode.jsonrpc4j.util.Util.param2; 28 | 29 | /** 30 | * This class validates the mapping of JSON-RPC result codes to HTTP status codes. 31 | */ 32 | @RunWith(EasyMockRunner.class) 33 | public class DefaultHttpStatusCodeProviderTest { 34 | 35 | @Mock(type = MockType.NICE) 36 | private JsonRpcBasicServerTest.ServiceInterface mockService; 37 | private JsonRpcServer jsonRpcServer; 38 | 39 | @Before 40 | public void setUp() throws Exception { 41 | jsonRpcServer = new JsonRpcServer(mapper, mockService, JsonRpcBasicServerTest.ServiceInterface.class); 42 | } 43 | 44 | @Test 45 | public void http404ForInvalidRequest() throws Exception { 46 | assertHttpStatusCodeForJsonRpcRequest(invalidJsonRpcRequestStream(), 404, jsonRpcServer); 47 | } 48 | 49 | public static void assertHttpStatusCodeForJsonRpcRequest(InputStream message, int expectedCode, JsonRpcServer server) throws Exception { 50 | MockHttpServletRequest req = new MockHttpServletRequest(); 51 | MockHttpServletResponse res = new MockHttpServletResponse(); 52 | req.setMethod(HttpMethod.POST.name()); 53 | req.setContent(convertInputStreamToByteArray(message)); 54 | server.handle(req, res); 55 | Assert.assertEquals(expectedCode, res.getStatus()); 56 | } 57 | 58 | @Test 59 | public void http400ForParseError() throws Exception { 60 | assertHttpStatusCodeForJsonRpcRequest(invalidJsonStream(), 400, jsonRpcServer); 61 | } 62 | 63 | @Test 64 | public void http200ForValidRequest() throws Exception { 65 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod", param1), 200, jsonRpcServer); 66 | } 67 | 68 | @Test 69 | public void http404ForNonExistingMethod() throws Exception { 70 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "nonExistingMethod", param1), 404, jsonRpcServer); 71 | } 72 | 73 | @Test 74 | public void http500ForInvalidMethodParameters() throws Exception { 75 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod", param1, param2), 500, jsonRpcServer); 76 | } 77 | 78 | @Test 79 | public void http500ForBulkErrors() throws Exception { 80 | assertHttpStatusCodeForJsonRpcRequest( 81 | multiMessageOfStream( 82 | messageWithListParams(1, "testMethod", param1, param2), 83 | messageWithListParams(2, "overloadedMethod", intParam1, intParam2) 84 | ), 500, jsonRpcServer); 85 | } 86 | 87 | @Test 88 | public void http500ForErrorNotHandled() throws Exception { 89 | JsonRpcServer server = new JsonRpcServer(mapper, mockService, JsonRpcErrorsTest.ServiceInterfaceWithoutAnnotation.class); 90 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod"), 500, server); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/rest/JsonRpcResponseErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.rest; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.http.client.ClientHttpResponse; 7 | import org.springframework.util.FileCopyUtils; 8 | import org.springframework.web.client.HttpClientErrorException; 9 | import org.springframework.web.client.HttpServerErrorException; 10 | import org.springframework.web.client.ResponseErrorHandler; 11 | import org.springframework.web.client.RestClientException; 12 | import org.springframework.web.client.UnknownHttpStatusCodeException; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.net.HttpURLConnection; 17 | import java.nio.charset.Charset; 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | public class JsonRpcResponseErrorHandler 24 | implements ResponseErrorHandler { 25 | 26 | public static final JsonRpcResponseErrorHandler INSTANCE = new JsonRpcResponseErrorHandler(); 27 | 28 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 29 | 30 | /** 31 | * for supported codes see {@link com.googlecode.jsonrpc4j.DefaultHttpStatusCodeProvider} 32 | */ 33 | private final Set JSON_RPC_STATUES = new HashSet(); 34 | 35 | 36 | private JsonRpcResponseErrorHandler() { 37 | JSON_RPC_STATUES.add(HttpURLConnection.HTTP_INTERNAL_ERROR); 38 | JSON_RPC_STATUES.add(HttpURLConnection.HTTP_BAD_REQUEST); 39 | JSON_RPC_STATUES.add(HttpURLConnection.HTTP_NOT_FOUND); 40 | } 41 | 42 | @Override 43 | public boolean hasError(ClientHttpResponse response) 44 | throws IOException { 45 | 46 | final HttpStatus httpStatus = getHttpStatusCode(response); 47 | 48 | if (JSON_RPC_STATUES.contains(httpStatus.value())) { 49 | // Checks the content type. If application/json-rpc then allow handler to read message 50 | final MediaType contentType = response.getHeaders().getContentType(); 51 | if (MappingJacksonRPC2HttpMessageConverter.APPLICATION_JSON_RPC.isCompatibleWith(contentType)) 52 | return false; 53 | } 54 | 55 | return 56 | httpStatus.series() == HttpStatus.Series.CLIENT_ERROR || 57 | httpStatus.series() == HttpStatus.Series.SERVER_ERROR; 58 | } 59 | 60 | @Override 61 | public void handleError(ClientHttpResponse response) 62 | throws IOException { 63 | final HttpStatus statusCode = getHttpStatusCode(response); 64 | 65 | switch (statusCode.series()) { 66 | case CLIENT_ERROR: 67 | throw new HttpClientErrorException(statusCode, response.getStatusText(), 68 | response.getHeaders(), getResponseBody(response), getCharset(response)); 69 | case SERVER_ERROR: 70 | throw new HttpServerErrorException(statusCode, response.getStatusText(), 71 | response.getHeaders(), getResponseBody(response), getCharset(response)); 72 | default: 73 | throw new RestClientException("Unknown status code [" + statusCode + "]"); 74 | } 75 | } 76 | 77 | 78 | private HttpStatus getHttpStatusCode(ClientHttpResponse response) throws IOException { 79 | final HttpStatus statusCode; 80 | try { 81 | statusCode = response.getStatusCode(); 82 | } catch (IllegalArgumentException ex) { 83 | throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), 84 | response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); 85 | } 86 | return statusCode; 87 | } 88 | 89 | private byte[] getResponseBody(ClientHttpResponse response) { 90 | try { 91 | final InputStream responseBody = response.getBody(); 92 | if (responseBody != null) { 93 | return FileCopyUtils.copyToByteArray(responseBody); 94 | } 95 | } catch (IOException ex) { 96 | // No body in response 401 for example 97 | logger.debug("Can not read resonse body", ex); 98 | } 99 | return new byte[0]; 100 | } 101 | 102 | private Charset getCharset(ClientHttpResponse response) { 103 | HttpHeaders headers = response.getHeaders(); 104 | MediaType contentType = headers.getContentType(); 105 | return contentType != null ? contentType.getCharset() : null; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/client/JsonRpcClientTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.client; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.googlecode.jsonrpc4j.JsonRpcClient; 5 | import com.googlecode.jsonrpc4j.RequestIDGenerator; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.ID; 17 | import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.PARAMS; 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertFalse; 20 | import static org.junit.Assert.assertTrue; 21 | import static org.junit.Assert.fail; 22 | 23 | public class JsonRpcClientTest { 24 | 25 | private ByteArrayOutputStream byteArrayOutputStream; 26 | private JsonRpcClient client; 27 | 28 | @Before 29 | public void setUp() { 30 | client = new JsonRpcClient(); 31 | byteArrayOutputStream = new ByteArrayOutputStream(); 32 | } 33 | 34 | @After 35 | public void tearDown() { 36 | client = null; 37 | } 38 | 39 | @Test 40 | public void testInvokeNoParams() throws Throwable { 41 | 42 | client.invoke("test", new Object[0], byteArrayOutputStream); 43 | JsonNode node = readJSON(byteArrayOutputStream); 44 | assertEquals(0, node.get(PARAMS).size()); 45 | } 46 | 47 | private JsonNode readJSON(ByteArrayOutputStream byteArrayOutputStream) throws IOException { 48 | return client.getObjectMapper().readTree(byteArrayOutputStream.toString(StandardCharsets.UTF_8.name())); 49 | } 50 | 51 | @Test 52 | public void testInvokeArrayParams() throws Throwable { 53 | client.invoke("test", new Object[]{1, 2}, byteArrayOutputStream); 54 | JsonNode node = readJSON(byteArrayOutputStream); 55 | 56 | assertTrue(node.has(PARAMS)); 57 | assertTrue(node.get(PARAMS).isArray()); 58 | assertEquals(1, node.get(PARAMS).get(0).intValue()); 59 | assertEquals(2, node.get(PARAMS).get(1).intValue()); 60 | } 61 | 62 | @Test 63 | public void testInvokeAdditionalJsonContent() throws Throwable { 64 | final String auth = "auth"; 65 | final String authValue = "secret"; 66 | client.setAdditionalJsonContent(new HashMap() { 67 | { 68 | put(auth, authValue); 69 | } 70 | }); 71 | client.invoke("test", new Object[]{1, 2}, byteArrayOutputStream); 72 | JsonNode node = readJSON(byteArrayOutputStream); 73 | 74 | assertTrue(node.has(auth)); 75 | assertEquals(node.get(auth).textValue(), authValue); 76 | } 77 | 78 | @Test 79 | public void testInvokeHashParams() throws Throwable { 80 | Map params = new HashMap<>(); 81 | params.put("hello", "test"); 82 | params.put("x", 1); 83 | client.invoke("test", params, byteArrayOutputStream); 84 | JsonNode node = readJSON(byteArrayOutputStream); 85 | 86 | assertTrue(node.has(PARAMS)); 87 | assertTrue(node.get(PARAMS).isObject()); 88 | assertEquals("test", node.get(PARAMS).get("hello").textValue()); 89 | assertEquals(1, node.get(PARAMS).get("x").intValue()); 90 | } 91 | 92 | @Test 93 | public void testIDGeneration() throws IOException { 94 | client.setRequestIDGenerator(new RequestIDGenerator() { 95 | @Override 96 | public String generateID() { 97 | return "test"; 98 | } 99 | }); 100 | 101 | Map params = new HashMap<>(); 102 | params.put("hello", "test"); 103 | params.put("x", 1); 104 | client.invoke("test", params, byteArrayOutputStream); 105 | JsonNode node = readJSON(byteArrayOutputStream); 106 | 107 | assertTrue(node.has(PARAMS)); 108 | assertTrue(node.get(PARAMS).isObject()); 109 | assertEquals("test", node.get(ID).asText()); 110 | } 111 | 112 | @Test 113 | public void testRandomIDGeneration() throws IOException { 114 | Map params = new HashMap<>(); 115 | params.put("hello", "test"); 116 | params.put("x", 1); 117 | client.invoke("test", params, byteArrayOutputStream); 118 | JsonNode node = readJSON(byteArrayOutputStream); 119 | 120 | assertTrue(node.has(PARAMS)); 121 | assertTrue(node.get(PARAMS).isObject()); 122 | try { 123 | Long.parseLong(node.get(ID).asText()); 124 | } catch (NumberFormatException e) { 125 | fail(); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/JsonRpcMultiServer.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.lang.reflect.Proxy; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * A multiple service dispatcher that supports JSON-RPC "method" names 13 | * that use dot-notation to select a server endpoint. For example: 14 | *
 15 |  * {
 16 |  *    "jsonrpc": "2.0",
 17 |  *    "method": "service.method",
 18 |  *    "params": {"foo", "bar"},
 19 |  *    "id": 1
 20 |  * }
 21 |  * 
22 | * An example of using this class is: 23 | * 24 | * JsonRpcMultiServer rpcServer = new JsonRpcMultiServer(); 25 | * rpcServer.addService("Foo", new FooService()) 26 | * .addService("Bar", new BarService()); 27 | * 28 | * A client can then call a test(String, String) method on the Foo service 29 | * like this: 30 | *
 31 |  * {
 32 |  *    "jsonrpc": "2.0",
 33 |  *    "method": "Foo.test",
 34 |  *    "params": ["some", "thing"],
 35 |  *    "id": 1
 36 |  * }
 37 |  * 
38 | */ 39 | @SuppressWarnings({"WeakerAccess", "unused"}) 40 | public class JsonRpcMultiServer extends JsonRpcServer { 41 | 42 | public static final char DEFAULT_SEPARATOR = '.'; 43 | private static final Logger logger = LoggerFactory.getLogger(JsonRpcMultiServer.class); 44 | 45 | private final Map handlerMap; 46 | private final Map> interfaceMap; 47 | private char separator = DEFAULT_SEPARATOR; 48 | 49 | public JsonRpcMultiServer() { 50 | this(new ObjectMapper()); 51 | logger.debug("created empty multi server"); 52 | } 53 | 54 | public JsonRpcMultiServer(ObjectMapper mapper) { 55 | super(mapper, (Object)null); 56 | this.handlerMap = new HashMap<>(); 57 | this.interfaceMap = new HashMap<>(); 58 | } 59 | 60 | public JsonRpcMultiServer addService(String name, Object handler) { 61 | return addService(name, handler, null); 62 | } 63 | 64 | public JsonRpcMultiServer addService(String name, Object handler, Class remoteInterface) { 65 | logger.debug("add service interface {} with handler {}", remoteInterface, handler); 66 | handlerMap.put(name, handler); 67 | if (remoteInterface != null) { 68 | interfaceMap.put(name, remoteInterface); 69 | } 70 | return this; 71 | } 72 | 73 | public char getSeparator() { 74 | return this.separator; 75 | } 76 | 77 | public void setSeparator(char separator) { 78 | this.separator = separator; 79 | } 80 | 81 | /** 82 | * Returns the handler's class or interfaces. The serviceName is used 83 | * to look up a registered handler. 84 | * 85 | * @param serviceName the optional name of a service 86 | * @return the class 87 | */ 88 | @Override 89 | protected Class[] getHandlerInterfaces(String serviceName) { 90 | Class remoteInterface = interfaceMap.get(serviceName); 91 | if (remoteInterface != null) { 92 | return new Class[]{remoteInterface}; 93 | } else if (Proxy.isProxyClass(getHandler(serviceName).getClass())) { 94 | return getHandler(serviceName).getClass().getInterfaces(); 95 | } else { 96 | return new Class[]{getHandler(serviceName).getClass()}; 97 | } 98 | } 99 | 100 | /** 101 | * Get the service name from the methodNode. JSON-RPC methods with the form 102 | * Service.method will result in "Service" being returned in this case. 103 | * 104 | * @param methodName method name 105 | * @return the name of the service, or null 106 | */ 107 | @Override 108 | protected String getServiceName(final String methodName) { 109 | if (methodName != null) { 110 | int ndx = methodName.indexOf(this.separator); 111 | if (ndx > 0) { 112 | return methodName.substring(0, ndx); 113 | } 114 | } 115 | return methodName; 116 | } 117 | 118 | /** 119 | * Get the method name from the methodNode, stripping off the service name. 120 | * 121 | * @param methodName the JsonNode for the method 122 | * @return the name of the method that should be invoked 123 | */ 124 | @Override 125 | protected String getMethodName(final String methodName) { 126 | if (methodName != null) { 127 | int ndx = methodName.indexOf(this.separator); 128 | if (ndx > 0) { 129 | return methodName.substring(ndx + 1); 130 | } 131 | } 132 | return methodName; 133 | } 134 | 135 | /** 136 | * Get the handler (object) that should be invoked to execute the specified 137 | * RPC method based on the specified service name. 138 | * 139 | * @param serviceName the service name 140 | * @return the handler to invoke the RPC call against 141 | */ 142 | @Override 143 | protected Object getHandler(String serviceName) { 144 | Object handler = handlerMap.get(serviceName); 145 | if (handler == null) { 146 | logger.error("Service '{}' is not registered in this multi-server", serviceName); 147 | throw new RuntimeException("Service '" + serviceName + "' does not exist"); 148 | } 149 | return handler; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcClientProxyFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcService; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | import org.springframework.core.type.AnnotationMetadata; 11 | import org.springframework.core.type.ClassMetadata; 12 | import org.springframework.core.type.classreading.MetadataReader; 13 | import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; 14 | import org.springframework.util.ReflectionUtils; 15 | 16 | import java.io.IOException; 17 | import java.net.MalformedURLException; 18 | import java.net.URL; 19 | 20 | 21 | public class AutoJsonRpcClientProxyFactory implements ApplicationContextAware, InstantiationAwareBeanPostProcessor { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(AutoJsonRpcClientProxyFactory.class); 24 | 25 | private ApplicationContext applicationContext; 26 | 27 | private URL baseUrl; 28 | 29 | @Override 30 | public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException { 31 | ReflectionUtils.doWithFields(bean.getClass(), field -> { 32 | if (field.isAnnotationPresent(JsonRpcReference.class)) { 33 | // valid 34 | final Class serviceInterface = field.getType(); 35 | if (!serviceInterface.isInterface()) { 36 | throw new RuntimeException("JsonRpcReference must be interface."); 37 | } 38 | 39 | JsonRpcReference rpcReference = field.getAnnotation(JsonRpcReference.class); 40 | String fullUrl = fullUrl(rpcReference.address(), serviceInterface); 41 | JsonProxyFactoryBean factoryBean = createJsonBean(serviceInterface, fullUrl); 42 | factoryBean.afterPropertiesSet(); 43 | // get proxy 44 | Object proxy = null; 45 | try { 46 | proxy = factoryBean.getObject(); 47 | } catch (Exception e) { 48 | throw new RuntimeException(e); 49 | } 50 | // set bean 51 | field.setAccessible(true); 52 | field.set(bean, proxy); 53 | logger.debug("------------- set bean {} field {} to proxy {}", beanName, field.getName(), proxy.getClass().getName()); 54 | } 55 | }); 56 | return true; 57 | } 58 | 59 | private String fullUrl(String url, Class serviceInterface) { 60 | try { 61 | baseUrl = new URL(url); 62 | } catch (MalformedURLException e) { 63 | throw new RuntimeException(e); 64 | } 65 | SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(applicationContext); 66 | MetadataReader metadataReader = null; 67 | try { 68 | metadataReader = metadataReaderFactory.getMetadataReader(serviceInterface.getName()); 69 | } catch (IOException e) { 70 | throw new RuntimeException(e); 71 | } 72 | ClassMetadata classMetadata = metadataReader.getClassMetadata(); 73 | AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); 74 | String jsonRpcPathAnnotation = JsonRpcService.class.getName(); 75 | if (annotationMetadata.isAnnotated(jsonRpcPathAnnotation)) { 76 | String className = classMetadata.getClassName(); 77 | String path = (String) annotationMetadata.getAnnotationAttributes(jsonRpcPathAnnotation).get("value"); 78 | logger.debug("Found JSON-RPC service to proxy [{}] on path '{}'.", className, path); 79 | return appendBasePath(path); 80 | } else { 81 | throw new RuntimeException("JsonRpcService must be interface."); 82 | } 83 | 84 | } 85 | 86 | private String appendBasePath(String path) { 87 | try { 88 | return new URL(baseUrl, path).toString(); 89 | } catch (MalformedURLException e) { 90 | throw new IllegalArgumentException(String.format("Cannot combine URLs '%s' and '%s' to valid URL.", baseUrl, path), e); 91 | } 92 | } 93 | 94 | 95 | private JsonProxyFactoryBean createJsonBean(Class serviceInterface, String serviceUrl) { 96 | JsonProxyFactoryBean bean = new JsonProxyFactoryBean(); 97 | bean.setServiceInterface(serviceInterface); 98 | bean.setServiceUrl(serviceUrl); 99 | return bean; 100 | } 101 | 102 | @Override 103 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 104 | this.applicationContext = applicationContext; 105 | } 106 | } 107 | 108 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/HttpStatusCodeProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.googlecode.jsonrpc4j.HttpStatusCodeProvider; 4 | import com.googlecode.jsonrpc4j.JsonRpcServer; 5 | import org.easymock.EasyMockRunner; 6 | import org.easymock.Mock; 7 | import org.easymock.MockType; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.BULK_ERROR; 13 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.ERROR_NOT_HANDLED; 14 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.INTERNAL_ERROR; 15 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.INVALID_REQUEST; 16 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.METHOD_NOT_FOUND; 17 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.METHOD_PARAMS_INVALID; 18 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.PARSE_ERROR; 19 | import static com.googlecode.jsonrpc4j.server.DefaultHttpStatusCodeProviderTest.assertHttpStatusCodeForJsonRpcRequest; 20 | import static com.googlecode.jsonrpc4j.util.Util.intParam1; 21 | import static com.googlecode.jsonrpc4j.util.Util.intParam2; 22 | import static com.googlecode.jsonrpc4j.util.Util.invalidJsonRpcRequestStream; 23 | import static com.googlecode.jsonrpc4j.util.Util.invalidJsonStream; 24 | import static com.googlecode.jsonrpc4j.util.Util.mapper; 25 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParams; 26 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParamsStream; 27 | import static com.googlecode.jsonrpc4j.util.Util.multiMessageOfStream; 28 | import static com.googlecode.jsonrpc4j.util.Util.param1; 29 | import static com.googlecode.jsonrpc4j.util.Util.param2; 30 | 31 | /** 32 | * These tests validate the functionality of a custom HttpStatusCodeProvider implementation. 33 | */ 34 | @RunWith(EasyMockRunner.class) 35 | public class HttpStatusCodeProviderTest { 36 | 37 | @Mock(type = MockType.NICE) 38 | private JsonRpcBasicServerTest.ServiceInterface mockService; 39 | private JsonRpcServer jsonRpcServer; 40 | private HttpStatusCodeProvider httpStatusCodeProvider; 41 | 42 | @Before 43 | public void setUp() throws Exception { 44 | jsonRpcServer = new JsonRpcServer(mapper, mockService, JsonRpcBasicServerTest.ServiceInterface.class); 45 | httpStatusCodeProvider = new HttpStatusCodeProvider() { 46 | @Override 47 | public int getHttpStatusCode(int resultCode) { 48 | if (resultCode == PARSE_ERROR.code) { 49 | return 1002; 50 | } else if (resultCode == INVALID_REQUEST.code) { 51 | return 1001; 52 | } else if (resultCode == METHOD_NOT_FOUND.code) { 53 | return 1003; 54 | } else if (resultCode == METHOD_PARAMS_INVALID.code) { 55 | return 1004; 56 | } else if (resultCode == INTERNAL_ERROR.code) { 57 | return 1007; 58 | } else if (resultCode == ERROR_NOT_HANDLED.code) { 59 | return 1006; 60 | } else if (resultCode == BULK_ERROR.code) { 61 | return 1005; 62 | } else { 63 | return 1000; 64 | } 65 | } 66 | 67 | @Override 68 | public Integer getJsonRpcCode(int httpStatusCode) { 69 | return null; 70 | } 71 | }; 72 | 73 | jsonRpcServer.setHttpStatusCodeProvider(httpStatusCodeProvider); 74 | } 75 | 76 | @Test 77 | public void http1001ForInvalidRequest() throws Exception { 78 | assertHttpStatusCodeForJsonRpcRequest(invalidJsonRpcRequestStream(), 1003, jsonRpcServer); 79 | } 80 | 81 | @Test 82 | public void http1002ForParseError() throws Exception { 83 | assertHttpStatusCodeForJsonRpcRequest(invalidJsonStream(), 1002, jsonRpcServer); 84 | } 85 | 86 | @Test 87 | public void http1000ForValidRequest() throws Exception { 88 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod", param1), 1000, jsonRpcServer); 89 | } 90 | 91 | @Test 92 | public void http1003ForNonExistingMethod() throws Exception { 93 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "nonExistingMethod", param1), 1003, jsonRpcServer); 94 | } 95 | 96 | @Test 97 | public void http1004ForInvalidMethodParameters() throws Exception { 98 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod", param1, param2), 1004, jsonRpcServer); 99 | } 100 | 101 | @Test 102 | public void http1005ForBulkErrors() throws Exception { 103 | assertHttpStatusCodeForJsonRpcRequest( 104 | multiMessageOfStream( 105 | messageWithListParams(1, "testMethod", param1, param2), 106 | messageWithListParams(2, "overloadedMethod", intParam1, intParam2) 107 | ), 1005, jsonRpcServer); 108 | } 109 | 110 | @Test 111 | public void http1006ForErrorNotHandled() throws Exception { 112 | JsonRpcServer server = new JsonRpcServer(mapper, mockService, JsonRpcErrorsTest.ServiceInterfaceWithoutAnnotation.class); 113 | server.setHttpStatusCodeProvider(httpStatusCodeProvider); 114 | assertHttpStatusCodeForJsonRpcRequest(messageWithListParamsStream(1, "testMethod"), 1006, server); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /publishing.gradle: -------------------------------------------------------------------------------- 1 | import io.codearte.gradle.nexus.BaseStagingTask 2 | import io.codearte.gradle.nexus.NexusStagingPlugin 3 | 4 | import javax.naming.ConfigurationException 5 | 6 | buildscript { 7 | repositories { 8 | gradlePluginPortal() 9 | } 10 | dependencies { 11 | classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0' 12 | classpath 'gradle.plugin.net.wooga.gradle:atlas-github:1.0.1' 13 | classpath 'net.researchgate:gradle-release:2.7.0' 14 | classpath 'org.ajoberstar:grgit:2.3.0' 15 | classpath 'org.kohsuke:github-api:1.93' 16 | } 17 | } 18 | 19 | ext { 20 | it.'signing.secretKeyRingFile' = project.findProperty('jsonrpc4j.signing.secretKeyRingFile') ?: 21 | project.findProperty('signing.secretKeyRingFile') 22 | it.'signing.password' = project.findProperty('jsonrpc4j.signing.password') ?: 23 | project.findProperty('signing.password') 24 | it.'signing.keyId' = project.findProperty('jsonrpc4j.signing.keyId') ?: 25 | project.findProperty('signing.keyId') 26 | sonatypeUsername = project.findProperty('jsonrpc4j.sonatype.username') ?: 27 | project.findProperty('sonatype.username') 28 | sonatypePassword = project.findProperty('jsonrpc4j.sonatype.password') ?: 29 | project.findProperty('sonatype.password') 30 | sonatypeStagingProfileId = project.findProperty('jsonrpc4j.sonatype.stagingProfileId') ?: 31 | project.findProperty('sonatype.stagingProfileId') 32 | it.'github.token' = project.findProperty('jsonrpc4j.github.token') ?: 33 | project.findProperty('github.token') 34 | } 35 | 36 | allprojects { 37 | apply plugin: 'maven-publish' 38 | 39 | publishing { 40 | repositories { 41 | maven { 42 | name 'sonatype' 43 | if (releaseVersion) { 44 | url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 45 | } else { 46 | url 'https://oss.sonatype.org/content/repositories/snapshots/' 47 | } 48 | credentials { 49 | username sonatypeUsername 50 | password sonatypePassword 51 | } 52 | } 53 | } 54 | } 55 | 56 | tasks.withType(PublishToMavenRepository) { 57 | doFirst { 58 | if (!sonatypeUsername) { 59 | throw new ConfigurationException( 60 | 'Please set the Sonatype username with project property "sonatype.username" ' + 61 | 'or "jsonrpc4j.sonatype.username". If both are set, the latter will be effective.') 62 | } 63 | if (!sonatypePassword) { 64 | throw new ConfigurationException( 65 | 'Please set the Sonatype password with project property "sonatype.password" ' + 66 | 'or "jsonrpc4j.sonatype.password". If both are set, the latter will be effective.') 67 | } 68 | } 69 | } 70 | } 71 | 72 | publishing { 73 | publications { 74 | jsonrpc4j(MavenPublication) { 75 | from components.java 76 | artifact documentationJar 77 | artifact sourcesJar 78 | } 79 | } 80 | } 81 | 82 | apply from: 'pom.gradle' 83 | 84 | allprojects { 85 | apply plugin: 'signing' 86 | 87 | signing { 88 | required { 89 | // signing is required if this is a release version and the artifacts are to be published 90 | releaseVersion && tasks.withType(PublishToMavenRepository).find { 91 | gradle.taskGraph.hasTask it 92 | } 93 | } 94 | sign publishing.publications 95 | } 96 | } 97 | 98 | apply plugin: NexusStagingPlugin 99 | // remove superfluous tasks from NexusStagingPlugin 100 | //tasks.removeAll([promoteRepository, closeAndPromoteRepository, getStagingProfile]) 101 | 102 | nexusStaging { 103 | stagingProfileId sonatypeStagingProfileId 104 | username sonatypeUsername 105 | password sonatypePassword 106 | } 107 | 108 | // make sure the staging tasks are run after any publishing tasks if both are to be run 109 | tasks.withType(BaseStagingTask) { 110 | mustRunAfter allprojects.tasks*.withType(PublishToMavenRepository) 111 | 112 | doFirst { 113 | if (!sonatypeStagingProfileId) { 114 | throw new ConfigurationException( 115 | 'Please set the Sonatype staging profile id with project property "sonatype.stagingProfileId" ' + 116 | 'or "jsonrpc4j.sonatype.stagingProfileId". If both are set, the latter will be effective.') 117 | } 118 | if (!sonatypeUsername) { 119 | throw new ConfigurationException( 120 | 'Please set the Sonatype username with project property "sonatype.username" ' + 121 | 'or "jsonrpc4j.sonatype.username". If both are set, the latter will be effective.') 122 | } 123 | if (!sonatypePassword) { 124 | throw new ConfigurationException( 125 | 'Please set the Sonatype password with project property "sonatype.password" ' + 126 | 'or "jsonrpc4j.sonatype.password". If both are set, the latter will be effective.') 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/JsonRpcErrorsTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.googlecode.jsonrpc4j.ErrorResolver; 5 | import com.googlecode.jsonrpc4j.JsonRpcBasicServer; 6 | import com.googlecode.jsonrpc4j.JsonRpcError; 7 | import com.googlecode.jsonrpc4j.JsonRpcErrors; 8 | import com.googlecode.jsonrpc4j.util.CustomTestException; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | 14 | import static com.googlecode.jsonrpc4j.util.Util.error; 15 | import static com.googlecode.jsonrpc4j.util.Util.errorCode; 16 | import static com.googlecode.jsonrpc4j.util.Util.errorData; 17 | import static com.googlecode.jsonrpc4j.util.Util.errorMessage; 18 | import static com.googlecode.jsonrpc4j.util.Util.exceptionType; 19 | import static com.googlecode.jsonrpc4j.util.Util.mapper; 20 | import static com.googlecode.jsonrpc4j.util.Util.messageWithListParamsStream; 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertNotNull; 23 | 24 | /** 25 | * For testing the @JsonRpcErrors and @JsonRpcError annotations 26 | */ 27 | public class JsonRpcErrorsTest { 28 | 29 | private ByteArrayOutputStream byteArrayOutputStream; 30 | private CustomTestException testException; 31 | private CustomTestException testExceptionWithMessage; 32 | 33 | @Before 34 | public void setup() { 35 | byteArrayOutputStream = new ByteArrayOutputStream(); 36 | testException = new CustomTestException(); 37 | testExceptionWithMessage = new CustomTestException("exception message"); 38 | } 39 | 40 | @Test 41 | public void exceptionWithoutAnnotatedServiceInterface() throws Exception { 42 | JsonRpcBasicServer jsonRpcServer = new JsonRpcBasicServer(mapper, new Service(), ServiceInterfaceWithoutAnnotation.class); 43 | jsonRpcServer.handleRequest(messageWithListParamsStream(1, "testMethod"), byteArrayOutputStream); 44 | JsonNode error = error(byteArrayOutputStream); 45 | assertNotNull(error); 46 | assertEquals(ErrorResolver.JsonError.ERROR_NOT_HANDLED.code, errorCode(error).intValue()); 47 | } 48 | 49 | @Test 50 | public void exceptionWithAnnotatedServiceInterface() throws Exception { 51 | JsonRpcBasicServer jsonRpcServer = new JsonRpcBasicServer(mapper, new Service(), ServiceInterfaceWithAnnotation.class); 52 | jsonRpcServer.handleRequest(messageWithListParamsStream(1, "testMethod"), byteArrayOutputStream); 53 | 54 | JsonNode error = error(byteArrayOutputStream); 55 | assertNotNull(error); 56 | assertEquals(1234, errorCode(error).intValue()); 57 | assertEquals(null, errorMessage(error).textValue()); 58 | JsonNode data = errorData(error); 59 | assertNotNull(data); 60 | assertEquals(null, errorMessage(data).textValue()); 61 | assertEquals(CustomTestException.class.getName(), exceptionType(data).textValue()); 62 | } 63 | 64 | @Test 65 | public void exceptionWithAnnotatedServiceInterfaceMessageAndData() throws Exception { 66 | JsonRpcBasicServer jsonRpcServer = new JsonRpcBasicServer(mapper, new Service(), ServiceInterfaceWithAnnotationMessageAndData.class); 67 | jsonRpcServer.handleRequest(messageWithListParamsStream(1, "testMethod"), byteArrayOutputStream); 68 | 69 | JsonNode error = error(byteArrayOutputStream); 70 | 71 | assertNotNull(error); 72 | assertEquals(-5678, errorCode(error).intValue()); 73 | assertEquals("The message", errorMessage(error).textValue()); 74 | } 75 | 76 | @Test 77 | public void exceptionWithMsgInException() throws Exception { 78 | JsonRpcBasicServer jsonRpcServer = new JsonRpcBasicServer(mapper, new ServiceWithExceptionMsg(), ServiceInterfaceWithAnnotation.class); 79 | jsonRpcServer.handleRequest(messageWithListParamsStream(1, "testMethod"), byteArrayOutputStream); 80 | 81 | JsonNode error = error(byteArrayOutputStream); 82 | 83 | assertNotNull(error); 84 | assertEquals(1234, errorCode(error).intValue()); 85 | assertEquals(testExceptionWithMessage.getMessage(), errorMessage(error).textValue()); 86 | JsonNode data = errorData(error); 87 | assertNotNull(data); 88 | assertEquals(testExceptionWithMessage.getMessage(), errorMessage(data).textValue()); 89 | assertEquals(CustomTestException.class.getName(), exceptionType(data).textValue()); 90 | } 91 | 92 | @SuppressWarnings({"unused", "WeakerAccess"}) 93 | public interface ServiceInterfaceWithoutAnnotation { 94 | Object testMethod(); 95 | } 96 | 97 | @SuppressWarnings({"unused", "WeakerAccess"}) 98 | public interface ServiceInterfaceWithAnnotation { 99 | @JsonRpcErrors({@JsonRpcError(exception = CustomTestException.class, code = 1234)}) 100 | Object testMethod(); 101 | } 102 | 103 | @SuppressWarnings({"unused", "WeakerAccess"}) 104 | public interface ServiceInterfaceWithAnnotationMessageAndData { 105 | @JsonRpcErrors({@JsonRpcError(exception = CustomTestException.class, code = -5678, message = "The message", data = "The data")}) 106 | Object testMethod(); 107 | } 108 | 109 | private class Service implements ServiceInterfaceWithoutAnnotation, ServiceInterfaceWithAnnotation, ServiceInterfaceWithAnnotationMessageAndData { 110 | public Object testMethod() { 111 | throw testException; 112 | } 113 | } 114 | 115 | private class ServiceWithExceptionMsg implements ServiceInterfaceWithoutAnnotation, ServiceInterfaceWithAnnotation, ServiceInterfaceWithAnnotationMessageAndData { 116 | public Object testMethod() { 117 | throw testExceptionWithMessage; 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcClientProxyCreator.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.JsonRpcService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 8 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 9 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 10 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.context.EnvironmentAware; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.core.io.Resource; 16 | import org.springframework.core.type.AnnotationMetadata; 17 | import org.springframework.core.type.ClassMetadata; 18 | import org.springframework.core.type.classreading.MetadataReader; 19 | import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; 20 | 21 | import java.io.IOException; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | 25 | import static java.lang.String.format; 26 | import static org.springframework.util.ClassUtils.convertClassNameToResourcePath; 27 | import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX; 28 | 29 | /** 30 | * Auto-creates proxies for service interfaces annotated with {@link JsonRpcService}. 31 | */ 32 | @SuppressWarnings("unused") 33 | public class AutoJsonRpcClientProxyCreator implements BeanFactoryPostProcessor, ApplicationContextAware, EnvironmentAware { 34 | 35 | private static final Logger logger = LoggerFactory.getLogger(AutoJsonRpcClientProxyCreator.class); 36 | private ApplicationContext applicationContext; 37 | private Environment environment; 38 | private String scanPackage; 39 | private URL baseUrl; 40 | private ObjectMapper objectMapper; 41 | private String contentType; 42 | 43 | @Override 44 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { 45 | SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(applicationContext); 46 | DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; 47 | String resolvedPath = resolvePackageToScan(); 48 | logger.debug("Scanning '{}' for JSON-RPC service interfaces.", resolvedPath); 49 | try { 50 | for (Resource resource : applicationContext.getResources(resolvedPath)) { 51 | if (resource.isReadable()) { 52 | MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); 53 | ClassMetadata classMetadata = metadataReader.getClassMetadata(); 54 | AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); 55 | String jsonRpcPathAnnotation = JsonRpcService.class.getName(); 56 | if (annotationMetadata.isAnnotated(jsonRpcPathAnnotation)) { 57 | String className = classMetadata.getClassName(); 58 | String path = (String) annotationMetadata.getAnnotationAttributes(jsonRpcPathAnnotation).get("value"); 59 | path = this.environment.resolvePlaceholders(path); 60 | logger.debug("Found JSON-RPC service to proxy [{}] on path '{}'.", className, path); 61 | registerJsonProxyBean(defaultListableBeanFactory, className, path); 62 | } 63 | } 64 | } 65 | } catch (IOException e) { 66 | throw new IllegalStateException(format("Cannot scan package '%s' for classes.", resolvedPath), e); 67 | } 68 | } 69 | 70 | /** 71 | * Converts the scanPackage to something that the resource loader can handleRequest. 72 | */ 73 | private String resolvePackageToScan() { 74 | return CLASSPATH_URL_PREFIX + convertClassNameToResourcePath(scanPackage) + "/**/*.class"; 75 | } 76 | 77 | /** 78 | * Registers a new proxy bean with the bean factory. 79 | */ 80 | private void registerJsonProxyBean(DefaultListableBeanFactory defaultListableBeanFactory, String className, String path) { 81 | BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder 82 | .rootBeanDefinition(JsonProxyFactoryBean.class) 83 | .addPropertyValue("serviceUrl", appendBasePath(path)) 84 | .addPropertyValue("serviceInterface", className); 85 | 86 | if (objectMapper != null) { 87 | beanDefinitionBuilder.addPropertyValue("objectMapper", objectMapper); 88 | } 89 | 90 | if (contentType != null) { 91 | beanDefinitionBuilder.addPropertyValue("contentType", contentType); 92 | } 93 | 94 | defaultListableBeanFactory.registerBeanDefinition(className + "-clientProxy", beanDefinitionBuilder.getBeanDefinition()); 95 | } 96 | 97 | /** 98 | * Appends the base path to the path found in the interface. 99 | */ 100 | private String appendBasePath(String path) { 101 | try { 102 | return new URL(baseUrl, path).toString(); 103 | } catch (MalformedURLException e) { 104 | throw new IllegalArgumentException(format("Cannot combine URLs '%s' and '%s' to valid URL.", baseUrl, path), e); 105 | } 106 | } 107 | 108 | @Override 109 | public void setApplicationContext(ApplicationContext applicationContext) { 110 | this.applicationContext = applicationContext; 111 | } 112 | 113 | 114 | @Override 115 | public void setEnvironment(Environment environment) { 116 | this.environment = environment; 117 | } 118 | 119 | public void setBaseUrl(URL baseUrl) { 120 | this.baseUrl = baseUrl; 121 | } 122 | 123 | public void setScanPackage(String scanPackage) { 124 | this.scanPackage = scanPackage; 125 | } 126 | 127 | public void setObjectMapper(ObjectMapper objectMapper) { 128 | this.objectMapper = objectMapper; 129 | } 130 | 131 | public void setContentType(String contextType) { 132 | this.contentType = contextType; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/ServerClientTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.googlecode.jsonrpc4j.ProxyUtil; 5 | import com.googlecode.jsonrpc4j.RequestInterceptor; 6 | import com.googlecode.jsonrpc4j.util.LocalThreadServer; 7 | import com.googlecode.jsonrpc4j.util.TestThrowable; 8 | import org.easymock.EasyMock; 9 | import org.easymock.EasyMockRunner; 10 | import org.easymock.Mock; 11 | import org.easymock.MockType; 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Rule; 15 | import org.junit.Test; 16 | import org.junit.rules.ExpectedException; 17 | import org.junit.runner.RunWith; 18 | 19 | import static com.googlecode.jsonrpc4j.util.Util.nonAsciiCharacters; 20 | import static com.googlecode.jsonrpc4j.util.Util.param1; 21 | import static com.googlecode.jsonrpc4j.util.Util.param3; 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.assertNotNull; 24 | 25 | @RunWith(EasyMockRunner.class) 26 | public class ServerClientTest { 27 | 28 | @Rule 29 | public final ExpectedException expectedEx = ExpectedException.none(); 30 | 31 | @Mock(type = MockType.NICE) 32 | private Service mockService; 33 | 34 | private Service client; 35 | private LocalThreadServer server; 36 | 37 | @Before 38 | public void setUp() throws Exception { 39 | server = new LocalThreadServer<>(mockService, Service.class); 40 | client = server.client(Service.class); 41 | } 42 | 43 | @After 44 | public void tearDown() throws Exception { 45 | server.close(); 46 | } 47 | 48 | @Test 49 | public void allMethods() throws Throwable { 50 | testCommon(client); 51 | } 52 | 53 | private void testCommon(Service client) throws Throwable { 54 | client.noOp(); 55 | EasyMock.expect(mockService.hello()).andReturn(param1); 56 | EasyMock.expect(mockService.hello(nonAsciiCharacters)).andReturn(nonAsciiCharacters); 57 | 58 | EasyMock.replay(mockService); 59 | client.noOp(); 60 | assertEquals(param1, client.hello()); 61 | assertEquals(nonAsciiCharacters, client.hello(nonAsciiCharacters)); 62 | assertNotNull(client.toString()); 63 | // noinspection ResultOfMethodCallIgnored 64 | client.hashCode(); 65 | EasyMock.verify(mockService); 66 | } 67 | 68 | @Test 69 | public void testAllMethodsViaCompositeProxy() throws Throwable { 70 | Object compositeService = ProxyUtil.createCompositeServiceProxy(ClassLoader.getSystemClassLoader(), new Object[]{client}, 71 | new Class[]{Service.class}, true); 72 | Service clientService = (Service) compositeService; 73 | testCommon(clientService); 74 | } 75 | 76 | @Test 77 | public void testNoArgFuncCallException() throws Throwable { 78 | final String message = "testing"; 79 | EasyMock.expect(mockService.hello()).andThrow(new TestThrowable(message)); 80 | EasyMock.replay(mockService); 81 | expectedEx.expectMessage(message); 82 | expectedEx.expect(TestThrowable.class); 83 | client.hello(); 84 | EasyMock.verify(mockService); 85 | } 86 | 87 | @Test 88 | public void testInterceptorDoingNothingCalled() throws Throwable { 89 | EasyMock.expect(mockService.hello()).andReturn(param1); 90 | RequestInterceptor interceptorMock = EasyMock.mock(RequestInterceptor.class); 91 | interceptorMock.interceptRequest(EasyMock.anyObject(JsonNode.class)); 92 | EasyMock.expectLastCall().andVoid(); 93 | server.setRequestInterceptor(interceptorMock); 94 | EasyMock.replay(interceptorMock, mockService); 95 | assertEquals(param1, client.hello()); 96 | EasyMock.verify(interceptorMock, mockService); 97 | } 98 | 99 | @Test 100 | public void testInterceptorRaisesException() throws Throwable { 101 | EasyMock.expect(mockService.hello()).andReturn(param1); 102 | RequestInterceptor interceptorMock = EasyMock.mock(RequestInterceptor.class); 103 | interceptorMock.interceptRequest(EasyMock.anyObject(JsonNode.class)); 104 | EasyMock.expectLastCall().andThrow(new TestThrowable(param3)); 105 | server.setRequestInterceptor(interceptorMock); 106 | expectedEx.expectMessage(param3); 107 | expectedEx.expect(TestThrowable.class); 108 | EasyMock.replay(interceptorMock, mockService); 109 | client.hello(); 110 | EasyMock.verify(interceptorMock, mockService); 111 | } 112 | 113 | @Test 114 | public void testOneArgFuncCallException() throws Throwable { 115 | final String name = "uranus"; 116 | final String message = name + " testing"; 117 | EasyMock.expect(mockService.hello(name)).andThrow(new TestThrowable(message)); 118 | EasyMock.replay(mockService); 119 | expectedEx.expectMessage(message); 120 | expectedEx.expect(TestThrowable.class); 121 | client.hello(name); 122 | EasyMock.verify(mockService); 123 | } 124 | 125 | @Test 126 | public void undeclaredException() { 127 | final String message = "testing"; 128 | mockService.undeclaredExceptionThrown(); 129 | EasyMock.expectLastCall().andThrow(new IllegalArgumentException(message)); 130 | EasyMock.replay(mockService); 131 | expectedEx.expect(IllegalArgumentException.class); 132 | expectedEx.expectMessage(message); 133 | client.undeclaredExceptionThrown(); 134 | } 135 | 136 | @Test 137 | public void unresolvedException() throws Throwable { 138 | final String message = "testing"; 139 | mockService.unresolvedExceptionThrown(); 140 | EasyMock.expectLastCall().andThrow(new IllegalArgumentException(message)); 141 | EasyMock.replay(mockService); 142 | expectedEx.expect(IllegalArgumentException.class); 143 | expectedEx.expectMessage(message); 144 | client.unresolvedExceptionThrown(); 145 | } 146 | 147 | public interface Service { 148 | void noOp(); 149 | 150 | String hello() throws Throwable; 151 | 152 | String hello(String world) throws Throwable; 153 | 154 | void unresolvedExceptionThrown() throws Throwable; 155 | 156 | void undeclaredExceptionThrown(); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/DefaultExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | 10 | import static com.googlecode.jsonrpc4j.Util.hasNonNullObjectData; 11 | import static com.googlecode.jsonrpc4j.Util.hasNonNullTextualData; 12 | 13 | /** 14 | * Default implementation of the {@link ExceptionResolver} interface that attempts to re-throw the same exception 15 | * that was thrown by the server. This always returns a {@link Throwable}. 16 | * The exception class must be present on the classpath. 17 | */ 18 | public class DefaultExceptionResolver implements ExceptionResolver { 19 | private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionResolver.class); 20 | public static final DefaultExceptionResolver INSTANCE = new DefaultExceptionResolver(); 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public Throwable resolveException(ObjectNode response) { 26 | ObjectNode errorObject = ObjectNode.class.cast(response.get(JsonRpcBasicServer.ERROR)); 27 | if (!hasNonNullObjectData(errorObject, JsonRpcBasicServer.DATA)) 28 | return createJsonRpcClientException(errorObject); 29 | 30 | ObjectNode dataObject = ObjectNode.class.cast(errorObject.get(JsonRpcBasicServer.DATA)); 31 | if (!hasNonNullTextualData(dataObject, JsonRpcBasicServer.EXCEPTION_TYPE_NAME)) 32 | return createJsonRpcClientException(errorObject); 33 | 34 | try { 35 | String exceptionTypeName = dataObject.get(JsonRpcBasicServer.EXCEPTION_TYPE_NAME).asText(); 36 | String message = hasNonNullTextualData(dataObject, JsonRpcBasicServer.ERROR_MESSAGE) ? dataObject.get(JsonRpcBasicServer.ERROR_MESSAGE).asText() : null; 37 | return createThrowable(exceptionTypeName, message); 38 | } catch (Exception e) { 39 | logger.warn("Unable to create throwable", e); 40 | return createJsonRpcClientException(errorObject); 41 | } 42 | } 43 | 44 | /** 45 | * Creates a {@link JsonRpcClientException} from the given 46 | * {@link ObjectNode}. 47 | * 48 | * @param errorObject the error object 49 | * @return the exception 50 | */ 51 | private JsonRpcClientException createJsonRpcClientException(ObjectNode errorObject) { 52 | int code = errorObject.has(JsonRpcBasicServer.ERROR_CODE) ? errorObject.get(JsonRpcBasicServer.ERROR_CODE).asInt() : 0; 53 | return new JsonRpcClientException(code, errorObject.get(JsonRpcBasicServer.ERROR_MESSAGE).asText(), errorObject.get(JsonRpcBasicServer.DATA)); 54 | } 55 | 56 | /** 57 | * Attempts to create an {@link Throwable} of the given type with the given message. For this method to create a 58 | * {@link Throwable} it must have either a default (no-args) constructor or a constructor that takes a {@code String} 59 | * as the message name. null is returned if a {@link Throwable} can't be created. 60 | * 61 | * @param typeName the java type name (class name) 62 | * @param message the message 63 | * @return the throwable 64 | * @throws InvocationTargetException 65 | * @throws IllegalAccessException 66 | * @throws InstantiationException 67 | * @throws IllegalArgumentException 68 | */ 69 | private Throwable createThrowable(String typeName, String message) throws IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { 70 | Class clazz = resolveThrowableClass(typeName); 71 | 72 | Constructor defaultCtr = getDefaultConstructor(clazz); 73 | Constructor messageCtr = getMessageConstructor(clazz); 74 | 75 | if (message != null && messageCtr != null) { 76 | return messageCtr.newInstance(message); 77 | } else if (message != null && defaultCtr != null) { 78 | logger.warn("Unable to invoke message constructor for {}, fallback to default", clazz.getName()); 79 | return defaultCtr.newInstance(); 80 | } else if (message == null && defaultCtr != null) { 81 | return defaultCtr.newInstance(); 82 | } else if (message == null && messageCtr != null) { 83 | logger.warn("Passing null message to message constructor for {}", clazz.getName()); 84 | return messageCtr.newInstance((String) null); 85 | } else { 86 | logger.error("Unable to find message or default constructor for {} have {}", clazz.getName(), clazz.getDeclaredConstructors()); 87 | return null; 88 | } 89 | } 90 | 91 | /** 92 | * Resolves original exception type name into an actual {@link Class}. 93 | * Override this, if you want custom behaviour for handling exceptions, 94 | * i.e.: Default to RuntimeException. 95 | * 96 | * @param typeName Original exception type name thrown on the server. 97 | * @return the resolved throwable 98 | * @throws ClassNotFoundException - if throwable class has not been found 99 | */ 100 | protected Class resolveThrowableClass(String typeName) throws ClassNotFoundException { 101 | Class clazz; 102 | try { 103 | clazz = Class.forName(typeName); 104 | if (!Throwable.class.isAssignableFrom(clazz)) { 105 | logger.warn("Type does not inherit from Throwable {}", clazz.getName()); 106 | } else { 107 | return clazz.asSubclass(Throwable.class); 108 | } 109 | } catch(ClassNotFoundException e) { 110 | logger.warn("Unable to load Throwable class {}", typeName); 111 | throw e; 112 | } catch(Exception e) { 113 | logger.warn("Unable to load Throwable class {}", typeName); 114 | } 115 | return null; 116 | } 117 | 118 | private Constructor getDefaultConstructor(Class clazz) { 119 | Constructor defaultCtr = null; 120 | try { 121 | defaultCtr = clazz.getConstructor(); 122 | } catch (NoSuchMethodException e) { 123 | handleException(e); 124 | } 125 | return defaultCtr; 126 | } 127 | 128 | private Constructor getMessageConstructor(Class clazz) { 129 | Constructor messageCtr = null; 130 | try { 131 | messageCtr = clazz.getConstructor(String.class); 132 | } catch (NoSuchMethodException e) { 133 | handleException(e); 134 | } 135 | return messageCtr; 136 | } 137 | 138 | @SuppressWarnings("UnusedParameters") 139 | private void handleException(Exception e) { 140 | /* do nothing */ 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/rest/JsonRestProxyFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.rest; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.ExceptionResolver; 5 | import com.googlecode.jsonrpc4j.JsonRpcClient; 6 | import com.googlecode.jsonrpc4j.JsonRpcClient.RequestListener; 7 | import com.googlecode.jsonrpc4j.ReflectionUtil; 8 | import org.aopalliance.intercept.MethodInterceptor; 9 | import org.aopalliance.intercept.MethodInvocation; 10 | import org.springframework.aop.framework.ProxyFactory; 11 | import org.springframework.beans.factory.BeanFactoryUtils; 12 | import org.springframework.beans.factory.FactoryBean; 13 | import org.springframework.beans.factory.InitializingBean; 14 | import org.springframework.context.ApplicationContext; 15 | import org.springframework.context.ApplicationContextAware; 16 | import org.springframework.remoting.support.UrlBasedRemoteAccessor; 17 | import org.springframework.web.client.RestTemplate; 18 | 19 | import javax.net.ssl.HostnameVerifier; 20 | import javax.net.ssl.SSLContext; 21 | import java.lang.reflect.Method; 22 | import java.lang.reflect.Type; 23 | import java.net.MalformedURLException; 24 | import java.net.URL; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * @param the bean type 30 | * @author toha 31 | */ 32 | @SuppressWarnings("unused") 33 | class JsonRestProxyFactoryBean extends UrlBasedRemoteAccessor implements MethodInterceptor, InitializingBean, FactoryBean, ApplicationContextAware { 34 | 35 | private T proxyObject = null; 36 | private RequestListener requestListener = null; 37 | private ObjectMapper objectMapper = null; 38 | private RestTemplate restTemplate = null; 39 | private JsonRpcRestClient jsonRpcRestClient = null; 40 | private Map extraHttpHeaders = new HashMap<>(); 41 | 42 | private SSLContext sslContext = null; 43 | private HostnameVerifier hostNameVerifier = null; 44 | 45 | 46 | private ExceptionResolver exceptionResolver; 47 | 48 | private ApplicationContext applicationContext; 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @Override 54 | public void afterPropertiesSet() { 55 | super.afterPropertiesSet(); 56 | 57 | proxyObject = ProxyFactory.getProxy(getObjectType(), this); 58 | 59 | if (jsonRpcRestClient==null) { 60 | 61 | if (objectMapper == null && applicationContext != null && applicationContext.containsBean("objectMapper")) { 62 | objectMapper = (ObjectMapper) applicationContext.getBean("objectMapper"); 63 | 64 | } 65 | if (objectMapper == null && applicationContext != null) { 66 | try { 67 | objectMapper = BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, ObjectMapper.class); 68 | } catch (Exception e) { 69 | logger.debug(e); 70 | } 71 | } 72 | 73 | if (objectMapper == null) { 74 | objectMapper = new ObjectMapper(); 75 | } 76 | 77 | try { 78 | jsonRpcRestClient = new JsonRpcRestClient(new URL(getServiceUrl()), objectMapper, restTemplate, new HashMap()); 79 | jsonRpcRestClient.setRequestListener(requestListener); 80 | jsonRpcRestClient.setSslContext(sslContext); 81 | jsonRpcRestClient.setHostNameVerifier(hostNameVerifier); 82 | 83 | if (exceptionResolver!=null) { 84 | jsonRpcRestClient.setExceptionResolver(exceptionResolver); 85 | } 86 | 87 | } catch (MalformedURLException mue) { 88 | throw new RuntimeException(mue); 89 | } 90 | 91 | } 92 | 93 | ReflectionUtil.clearCache(); 94 | } 95 | 96 | /** 97 | * {@inheritDoc} 98 | */ 99 | @Override 100 | public Object invoke(MethodInvocation invocation) throws Throwable { 101 | Method method = invocation.getMethod(); 102 | if (method.getDeclaringClass() == Object.class && method.getName().equals("toString")) { 103 | return proxyObject.getClass().getName() + "@" + System.identityHashCode(proxyObject); 104 | } 105 | 106 | Type retType = (invocation.getMethod().getGenericReturnType() != null) ? invocation.getMethod().getGenericReturnType() : invocation.getMethod().getReturnType(); 107 | Object arguments = ReflectionUtil.parseArguments(invocation.getMethod(), invocation.getArguments()); 108 | 109 | return jsonRpcRestClient.invoke(invocation.getMethod().getName(), arguments, retType, extraHttpHeaders); 110 | } 111 | 112 | /** 113 | * {@inheritDoc} 114 | */ 115 | @Override 116 | public T getObject() { 117 | return proxyObject; 118 | } 119 | 120 | /** 121 | * {@inheritDoc} 122 | */ 123 | @SuppressWarnings("unchecked") 124 | @Override 125 | public Class getObjectType() { 126 | return (Class) getServiceInterface(); 127 | } 128 | 129 | /** 130 | * {@inheritDoc} 131 | */ 132 | @Override 133 | public boolean isSingleton() { 134 | return true; 135 | } 136 | 137 | /** 138 | * {@inheritDoc} 139 | */ 140 | @Override 141 | public void setApplicationContext(ApplicationContext applicationContext) { 142 | this.applicationContext = applicationContext; 143 | } 144 | 145 | /** 146 | * @param objectMapper the objectMapper to set 147 | */ 148 | public void setObjectMapper(ObjectMapper objectMapper) { 149 | this.objectMapper = objectMapper; 150 | } 151 | 152 | /** 153 | * @param extraHttpHeaders the extraHttpHeaders to set 154 | */ 155 | public void setExtraHttpHeaders(Map extraHttpHeaders) { 156 | this.extraHttpHeaders = extraHttpHeaders; 157 | } 158 | 159 | /** 160 | * @param requestListener the requestListener to set 161 | */ 162 | public void setRequestListener(JsonRpcClient.RequestListener requestListener) { 163 | this.requestListener = requestListener; 164 | } 165 | 166 | /** 167 | * @param sslContext SSL contest for JsonRpcClient 168 | */ 169 | public void setSslContext(SSLContext sslContext) { 170 | this.sslContext = sslContext; 171 | } 172 | 173 | /** 174 | * @param hostNameVerifier the hostNameVerifier to pass to JsonRpcClient 175 | */ 176 | public void setHostNameVerifier(HostnameVerifier hostNameVerifier) { 177 | this.hostNameVerifier = hostNameVerifier; 178 | } 179 | 180 | /** 181 | * @param restTemplate external RestTemplate 182 | */ 183 | public void setRestTemplate(RestTemplate restTemplate) { 184 | this.restTemplate = restTemplate; 185 | } 186 | 187 | public void setJsonRpcRestClient(JsonRpcRestClient jsonRpcRestClient) { 188 | this.jsonRpcRestClient = jsonRpcRestClient; 189 | } 190 | 191 | public void setExceptionResolver(ExceptionResolver exceptionResolver) { 192 | this.exceptionResolver = exceptionResolver; 193 | } 194 | 195 | 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/JsonProxyFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.JsonRpcClient.RequestListener; 5 | import com.googlecode.jsonrpc4j.ExceptionResolver; 6 | import com.googlecode.jsonrpc4j.JsonRpcHttpClient; 7 | import com.googlecode.jsonrpc4j.ProxyUtil; 8 | import com.googlecode.jsonrpc4j.ReflectionUtil; 9 | import org.aopalliance.intercept.MethodInterceptor; 10 | import org.aopalliance.intercept.MethodInvocation; 11 | import org.springframework.aop.framework.ProxyFactory; 12 | import org.springframework.beans.factory.BeanFactoryUtils; 13 | import org.springframework.beans.factory.FactoryBean; 14 | import org.springframework.beans.factory.InitializingBean; 15 | import org.springframework.context.ApplicationContext; 16 | import org.springframework.context.ApplicationContextAware; 17 | import org.springframework.remoting.support.UrlBasedRemoteAccessor; 18 | 19 | import javax.net.ssl.HostnameVerifier; 20 | import javax.net.ssl.SSLContext; 21 | import java.lang.reflect.Method; 22 | import java.lang.reflect.Type; 23 | import java.net.MalformedURLException; 24 | import java.net.URL; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * {@link FactoryBean} for creating a {@link UrlBasedRemoteAccessor} 30 | * (aka consumer) for accessing an HTTP based JSON-RPC service. 31 | */ 32 | @SuppressWarnings("unused") 33 | public class JsonProxyFactoryBean extends UrlBasedRemoteAccessor implements MethodInterceptor, InitializingBean, FactoryBean, ApplicationContextAware { 34 | 35 | private Object proxyObject = null; 36 | private RequestListener requestListener = null; 37 | private ObjectMapper objectMapper = null; 38 | private JsonRpcHttpClient jsonRpcHttpClient = null; 39 | private Map extraHttpHeaders = new HashMap<>(); 40 | private String contentType; 41 | 42 | private SSLContext sslContext = null; 43 | private HostnameVerifier hostNameVerifier = null; 44 | 45 | private ExceptionResolver exceptionResolver; 46 | 47 | private ApplicationContext applicationContext; 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | @Override 53 | @SuppressWarnings("unchecked") 54 | public void afterPropertiesSet() { 55 | super.afterPropertiesSet(); 56 | proxyObject = ProxyFactory.getProxy(getServiceInterface(), this); 57 | 58 | if (jsonRpcHttpClient==null) { 59 | if (objectMapper == null && applicationContext != null && applicationContext.containsBean("objectMapper")) { 60 | objectMapper = (ObjectMapper) applicationContext.getBean("objectMapper"); 61 | } 62 | if (objectMapper == null && applicationContext != null) { 63 | try { 64 | objectMapper = BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, ObjectMapper.class); 65 | } catch (Exception e) { 66 | logger.debug(e); 67 | } 68 | } 69 | if (objectMapper == null) { 70 | objectMapper = new ObjectMapper(); 71 | } 72 | 73 | try { 74 | jsonRpcHttpClient = new JsonRpcHttpClient(objectMapper, new URL(getServiceUrl()), extraHttpHeaders); 75 | jsonRpcHttpClient.setRequestListener(requestListener); 76 | jsonRpcHttpClient.setSslContext(sslContext); 77 | jsonRpcHttpClient.setHostNameVerifier(hostNameVerifier); 78 | 79 | if (contentType != null) { 80 | jsonRpcHttpClient.setContentType(contentType); 81 | } 82 | 83 | if (exceptionResolver!=null) { 84 | jsonRpcHttpClient.setExceptionResolver(exceptionResolver); 85 | } 86 | } catch (MalformedURLException mue) { 87 | throw new RuntimeException(mue); 88 | } 89 | } 90 | 91 | ReflectionUtil.clearCache(); 92 | } 93 | 94 | /** 95 | * {@inheritDoc} 96 | */ 97 | @Override 98 | public Object invoke(MethodInvocation invocation) 99 | throws Throwable { 100 | Method method = invocation.getMethod(); 101 | if (method.getDeclaringClass() == Object.class && method.getName().equals("toString")) { 102 | return proxyObject.getClass().getName() + "@" + System.identityHashCode(proxyObject); 103 | } 104 | 105 | Type retType = (invocation.getMethod().getGenericReturnType() != null) ? invocation.getMethod().getGenericReturnType() : invocation.getMethod().getReturnType(); 106 | Object arguments = ReflectionUtil.parseArguments(invocation.getMethod(), invocation.getArguments()); 107 | 108 | return jsonRpcHttpClient.invoke(ProxyUtil.getMethodName(method), arguments, retType, extraHttpHeaders); 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | @Override 115 | public Object getObject() { 116 | return proxyObject; 117 | } 118 | 119 | /** 120 | * {@inheritDoc} 121 | */ 122 | @Override 123 | public Class getObjectType() { 124 | return getServiceInterface(); 125 | } 126 | 127 | /** 128 | * {@inheritDoc} 129 | */ 130 | @Override 131 | public boolean isSingleton() { 132 | return true; 133 | } 134 | 135 | /** 136 | * {@inheritDoc} 137 | */ 138 | @Override 139 | public void setApplicationContext(ApplicationContext applicationContext) { 140 | this.applicationContext = applicationContext; 141 | } 142 | 143 | /** 144 | * @param objectMapper the objectMapper to set 145 | */ 146 | public void setObjectMapper(ObjectMapper objectMapper) { 147 | this.objectMapper = objectMapper; 148 | } 149 | 150 | /** 151 | * @param extraHttpHeaders the extraHttpHeaders to set 152 | */ 153 | public void setExtraHttpHeaders(Map extraHttpHeaders) { 154 | this.extraHttpHeaders = extraHttpHeaders; 155 | } 156 | 157 | /** 158 | * @param requestListener the requestListener to set 159 | */ 160 | public void setRequestListener(RequestListener requestListener) { 161 | this.requestListener = requestListener; 162 | } 163 | 164 | /** 165 | * @param sslContext SSL context to pass to JsonRpcClient 166 | */ 167 | public void setSslContext(SSLContext sslContext) { 168 | this.sslContext = sslContext; 169 | } 170 | 171 | /** 172 | * @param hostNameVerifier the hostNameVerifier to pass to JsonRpcClient 173 | */ 174 | public void setHostNameVerifier(HostnameVerifier hostNameVerifier) { 175 | this.hostNameVerifier = hostNameVerifier; 176 | } 177 | 178 | /** 179 | * @param contentType the contentType to pass to JsonRpcClient 180 | */ 181 | public void setContentType(String contentType) { 182 | this.contentType = contentType; 183 | } 184 | 185 | public void setJsonRpcHttpClient(JsonRpcHttpClient jsonRpcHttpClient) { 186 | this.jsonRpcHttpClient = jsonRpcHttpClient; 187 | } 188 | 189 | public void setExceptionResolver(ExceptionResolver exceptionResolver) { 190 | this.exceptionResolver = exceptionResolver; 191 | } 192 | 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.util; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.googlecode.jsonrpc4j.JsonRpcBasicServer; 7 | import com.googlecode.jsonrpc4j.JsonRpcServer; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Arrays; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public class Util { 19 | public static final String param1 = "param1"; 20 | public static final String nonAsciiCharacters = "PñA&s<>k ;"; 21 | public static final String param2 = "param2"; 22 | public static final String param3 = "param3"; 23 | public static final String param4 = "param4"; 24 | public static final int intParam1 = 1; 25 | public static final int intParam2 = 2; 26 | public static final long longParam = 1600000000000L; 27 | public static final String JSON_ENCODING = StandardCharsets.UTF_8.name(); 28 | public static final ObjectMapper mapper = new ObjectMapper(); 29 | @SuppressWarnings("PMD.AvoidUsingHardCodedIP") 30 | public static final String DEFAULT_LOCAL_HOSTNAME = "127.0.0.1"; 31 | private static final String invalidJsonRpcRequest = "{\"method\": \"subtract\", \"params\": [], \"id\": 1}"; 32 | private static final String invalidJson = "{\"jsonrpc\": \"2.0,\n" + 33 | " \"method\": \"testMethod\",\n" + 34 | " \"params\": {},\n" + 35 | " \"id\": \n" + 36 | " }\n" + 37 | " "; 38 | private static final String emptyLine = "\n"; 39 | 40 | public static InputStream invalidJsonRpcRequestStream() { 41 | return new ByteArrayInputStream(invalidJsonRpcRequest.getBytes(StandardCharsets.UTF_8)); 42 | } 43 | 44 | public static InputStream invalidJsonStream() { 45 | return new ByteArrayInputStream(invalidJson.getBytes(StandardCharsets.UTF_8)); 46 | } 47 | 48 | public static InputStream emptyLineStream() { 49 | return new ByteArrayInputStream(emptyLine.getBytes(StandardCharsets.UTF_8)); 50 | } 51 | 52 | public static InputStream messageWithListParamsStream(final Object id, final String methodName, final Object... args) throws JsonProcessingException { 53 | return createStream(messageWithListParams(id, methodName, args)); 54 | } 55 | 56 | public static InputStream createStream(Object content) throws JsonProcessingException { 57 | String data = mapper.writeValueAsString(content); 58 | return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); 59 | } 60 | 61 | public static HashMap messageWithListParams(final Object id, final String methodName, final Object... args) throws JsonProcessingException { 62 | return messageOfStream(id, methodName, Arrays.asList(args)); 63 | } 64 | 65 | public static HashMap messageOfStream(final Object id, final String methodName, final Object params) { 66 | return makeJsonRpcRequestObject(id, methodName, params); 67 | } 68 | 69 | @SuppressWarnings("serial") 70 | private static HashMap makeJsonRpcRequestObject(final Object id, final String methodName, final Object params) { 71 | return new HashMap() { 72 | { 73 | if (id != null) put(JsonRpcBasicServer.ID, id); 74 | put(JsonRpcBasicServer.JSONRPC, JsonRpcServer.VERSION); 75 | if (methodName != null) put(JsonRpcBasicServer.METHOD, methodName); 76 | if (params != null) put(JsonRpcBasicServer.PARAMS, params); 77 | } 78 | }; 79 | } 80 | 81 | public static InputStream multiMessageOfStream(Object... args) throws JsonProcessingException { 82 | return createStream(args); 83 | } 84 | 85 | public static InputStream messageWithMapParamsStream(final String methodName, final Object... args) throws JsonProcessingException { 86 | return createStream(messageWithMapParams(methodName, args)); 87 | } 88 | 89 | private static HashMap messageWithMapParams(final String methodName, final Object... args) throws JsonProcessingException { 90 | Map elements = new HashMap<>(); 91 | for (int i = 0; i < args.length; i += 2) { 92 | final String key = (String) args[i]; 93 | final Object value = args[i + 1]; 94 | elements.put(key, value); 95 | } 96 | return messageOfStream(1, methodName, elements); 97 | } 98 | 99 | public static ByteArrayOutputStream toByteArrayOutputStream(byte[] data) throws IOException { 100 | ByteArrayOutputStream result = new ByteArrayOutputStream(data.length); 101 | result.write(data); 102 | return result; 103 | } 104 | 105 | public static ByteArrayOutputStream toByteArrayOutputStream(InputStream inputStream) throws IOException { 106 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 107 | byte[] buffer = new byte[256]; 108 | int read; 109 | 110 | while (-1 != (read = inputStream.read(buffer))) { 111 | result.write(buffer, 0, read); 112 | } 113 | 114 | return result; 115 | } 116 | 117 | public static JsonNode error(ByteArrayOutputStream byteArrayOutputStream) throws IOException { 118 | return decodeAnswer(byteArrayOutputStream).get(JsonRpcBasicServer.ERROR); 119 | } 120 | 121 | public static JsonNode decodeAnswer(ByteArrayOutputStream byteArrayOutputStream) throws IOException { 122 | return mapper.readTree(byteArrayOutputStream.toString(JSON_ENCODING)); 123 | } 124 | 125 | public static JsonNode errorCode(JsonNode error) { 126 | return error.get(JsonRpcBasicServer.ERROR_CODE); 127 | } 128 | 129 | public static JsonNode errorMessage(JsonNode error) { 130 | return error.get(JsonRpcBasicServer.ERROR_MESSAGE); 131 | } 132 | 133 | public static JsonNode errorData(JsonNode error) { 134 | return error.get(JsonRpcBasicServer.DATA); 135 | } 136 | 137 | public static JsonNode exceptionType(JsonNode error) { 138 | return error.get(JsonRpcBasicServer.EXCEPTION_TYPE_NAME); 139 | } 140 | 141 | 142 | public static JsonNode getFromArrayWithId(final JsonNode node, final int id) { 143 | for (JsonNode n : node) { 144 | if (n.get(JsonRpcBasicServer.ID).asInt() == id) { 145 | return n; 146 | } 147 | } 148 | throw new IllegalStateException("could not find in " + node + " id " + id); 149 | } 150 | 151 | /** 152 | * Simple input stream to byte array converter. 153 | * 154 | * @param inputStream the input stream that will be converted. 155 | * @return the content of the input stream in form of a byte array. 156 | * @throws IOException thrown if there was an IO error while converting data. 157 | */ 158 | public static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException { 159 | 160 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 161 | 162 | int read; 163 | byte[] data = new byte[512]; 164 | while ((read = inputStream.read(data, 0, data.length)) != -1) { 165 | buffer.write(data, 0, read); 166 | } 167 | 168 | buffer.flush(); 169 | return buffer.toByteArray(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/AbstractCompositeJsonServiceExporter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.googlecode.jsonrpc4j.*; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.BeanFactoryUtils; 8 | import org.springframework.beans.factory.InitializingBean; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | 12 | /** 13 | * Abstract class for exposing composite services via spring. 14 | */ 15 | @SuppressWarnings("unused") 16 | abstract class AbstractCompositeJsonServiceExporter implements InitializingBean, ApplicationContextAware { 17 | private static final Logger logger = LoggerFactory.getLogger(AbstractCompositeJsonServiceExporter.class); 18 | 19 | private ObjectMapper objectMapper; 20 | private ApplicationContext applicationContext; 21 | private ErrorResolver errorResolver = null; 22 | private boolean backwardsCompatible = true; 23 | private boolean rethrowExceptions = false; 24 | private boolean allowExtraParams = false; 25 | private boolean allowLessParams = false; 26 | 27 | private boolean shouldLogInvocationErrors = true; 28 | private InvocationListener invocationListener = null; 29 | private HttpStatusCodeProvider httpStatusCodeProvider = null; 30 | private ConvertedParameterTransformer convertedParameterTransformer = null; 31 | private String contentType = null; 32 | 33 | private JsonRpcServer jsonRpcServer; 34 | 35 | private boolean allowMultipleInheritance = false; 36 | private Class[] serviceInterfaces = null; 37 | private Object[] services = new Object[0]; 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public final void afterPropertiesSet() 44 | throws Exception { 45 | 46 | // find the ObjectMapper 47 | if (objectMapper == null 48 | && applicationContext != null 49 | && applicationContext.containsBean("objectMapper")) { 50 | objectMapper = (ObjectMapper) applicationContext.getBean("objectMapper"); 51 | } 52 | if (objectMapper == null && applicationContext != null) { 53 | try { 54 | objectMapper = BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, ObjectMapper.class); 55 | } catch (Exception e) { 56 | logger.error("Could not load ObjectMapper from ApplicationContext", e); 57 | } 58 | } 59 | if (objectMapper == null) { 60 | objectMapper = new ObjectMapper(); 61 | } 62 | 63 | // create the service 64 | Object service = ProxyUtil.createCompositeServiceProxy( 65 | getClass().getClassLoader(), services, 66 | serviceInterfaces, allowMultipleInheritance); 67 | 68 | // create the server 69 | jsonRpcServer = new JsonRpcServer(objectMapper, service); 70 | jsonRpcServer.setErrorResolver(errorResolver); 71 | jsonRpcServer.setBackwardsCompatible(backwardsCompatible); 72 | jsonRpcServer.setRethrowExceptions(rethrowExceptions); 73 | jsonRpcServer.setAllowExtraParams(allowExtraParams); 74 | jsonRpcServer.setAllowLessParams(allowLessParams); 75 | 76 | jsonRpcServer.setInvocationListener(invocationListener); 77 | jsonRpcServer.setHttpStatusCodeProvider(httpStatusCodeProvider); 78 | jsonRpcServer.setConvertedParameterTransformer(convertedParameterTransformer); 79 | jsonRpcServer.setShouldLogInvocationErrors(shouldLogInvocationErrors); 80 | 81 | if (contentType != null) { 82 | jsonRpcServer.setContentType(contentType); 83 | } 84 | 85 | ReflectionUtil.clearCache(); 86 | 87 | // export 88 | exportService(); 89 | } 90 | 91 | /** 92 | * Called when the service is ready to be exported. 93 | * 94 | * @throws Exception on error 95 | */ 96 | protected void exportService() 97 | throws Exception { 98 | // no-op 99 | } 100 | 101 | /** 102 | * @return the jsonRpcServer 103 | */ 104 | JsonRpcServer getJsonRpcServer() { 105 | return jsonRpcServer; 106 | } 107 | 108 | /** 109 | * @param objectMapper the objectMapper to set 110 | */ 111 | public void setObjectMapper(ObjectMapper objectMapper) { 112 | this.objectMapper = objectMapper; 113 | } 114 | 115 | /** 116 | * @param applicationContext the applicationContext to set 117 | */ 118 | public void setApplicationContext(ApplicationContext applicationContext) { 119 | this.applicationContext = applicationContext; 120 | } 121 | 122 | /** 123 | * @param errorResolver the errorResolver to set 124 | */ 125 | public void setErrorResolver(ErrorResolver errorResolver) { 126 | this.errorResolver = errorResolver; 127 | } 128 | 129 | /** 130 | * @param backwardsCompatible the backwardsCompatible to set 131 | */ 132 | public void setBackwardsCompatible(boolean backwardsCompatible) { 133 | this.backwardsCompatible = backwardsCompatible; 134 | } 135 | 136 | /** 137 | * @param rethrowExceptions the rethrowExceptions to set 138 | */ 139 | public void setRethrowExceptions(boolean rethrowExceptions) { 140 | this.rethrowExceptions = rethrowExceptions; 141 | } 142 | 143 | /** 144 | * @param allowExtraParams the allowExtraParams to set 145 | */ 146 | public void setAllowExtraParams(boolean allowExtraParams) { 147 | this.allowExtraParams = allowExtraParams; 148 | } 149 | 150 | /** 151 | * @param allowLessParams the allowLessParams to set 152 | */ 153 | public void setAllowLessParams(boolean allowLessParams) { 154 | this.allowLessParams = allowLessParams; 155 | } 156 | 157 | /** 158 | * @param invocationListener the invocationListener to set 159 | */ 160 | public void setInvocationListener(InvocationListener invocationListener) { 161 | this.invocationListener = invocationListener; 162 | } 163 | 164 | /** 165 | * @param httpStatusCodeProvider the HttpStatusCodeProvider to set 166 | */ 167 | public void setHttpStatusCodeProvider(HttpStatusCodeProvider httpStatusCodeProvider) { 168 | this.httpStatusCodeProvider = httpStatusCodeProvider; 169 | } 170 | 171 | /** 172 | * @param convertedParameterTransformer the convertedParameterTransformer to set 173 | */ 174 | public void setConvertedParameterTransformer(ConvertedParameterTransformer convertedParameterTransformer) { 175 | this.convertedParameterTransformer = convertedParameterTransformer; 176 | } 177 | 178 | public void setShouldLogInvocationErrors(boolean shouldLogInvocationErrors) { 179 | this.shouldLogInvocationErrors = shouldLogInvocationErrors; 180 | } 181 | 182 | public void setContentType(String contentType) { 183 | this.contentType = contentType; 184 | } 185 | 186 | 187 | /** 188 | * @param allowMultipleInheritance the allowMultipleInheritance to set 189 | */ 190 | public void setAllowMultipleInheritance(boolean allowMultipleInheritance) { 191 | this.allowMultipleInheritance = allowMultipleInheritance; 192 | } 193 | 194 | /** 195 | * @param serviceInterfaces the serviceInterfaces to set 196 | */ 197 | public void setServiceInterfaces(Class[] serviceInterfaces) { 198 | this.serviceInterfaces = serviceInterfaces; 199 | } 200 | 201 | /** 202 | * @param services the services to set 203 | */ 204 | public void setServices(Object[] services) { 205 | this.services = services; 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/integration/StreamServerTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.integration; 2 | 3 | import com.googlecode.jsonrpc4j.JsonRpcBasicServer; 4 | import com.googlecode.jsonrpc4j.JsonRpcClient; 5 | import com.googlecode.jsonrpc4j.ProxyUtil; 6 | import com.googlecode.jsonrpc4j.StreamServer; 7 | import com.googlecode.jsonrpc4j.StreamServer.Server; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import javax.net.ServerSocketFactory; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.net.InetAddress; 18 | import java.net.ServerSocket; 19 | import java.net.Socket; 20 | 21 | import static com.googlecode.jsonrpc4j.util.Util.DEFAULT_LOCAL_HOSTNAME; 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.assertNotNull; 24 | 25 | public class StreamServerTest { 26 | 27 | private static final Logger logger = LoggerFactory.getLogger(StreamServerTest.class); 28 | private ServerSocket serverSocket; 29 | private JsonRpcBasicServer jsonRpcServer; 30 | private JsonRpcClient jsonRpcClient; 31 | private ServiceImpl service; 32 | 33 | @Before 34 | public void setUp() throws Exception { 35 | serverSocket = ServerSocketFactory.getDefault().createServerSocket(0, 0, InetAddress.getByName(DEFAULT_LOCAL_HOSTNAME)); 36 | service = new ServiceImpl(); 37 | jsonRpcServer = new JsonRpcBasicServer(service, Service.class); 38 | jsonRpcClient = new JsonRpcClient(); 39 | } 40 | 41 | @Test 42 | public void testBasicConnection() throws Exception { 43 | 44 | StreamServer streamServer = createAndStartServer(); 45 | Socket socket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 46 | Service client = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), Service.class, jsonRpcClient, socket); 47 | for (int i = 0; i < 100; i++) { 48 | assertEquals(i, client.inc()); 49 | } 50 | assertEquals("hello dude", client.hello("dude")); 51 | socket.close(); 52 | streamServer.stop(); 53 | } 54 | 55 | private StreamServer createAndStartServer() { 56 | StreamServer streamServer = new StreamServer(jsonRpcServer, 5, serverSocket); 57 | streamServer.start(); 58 | return streamServer; 59 | } 60 | 61 | @Test 62 | public void testMultipleClients() throws Exception { 63 | StreamServer streamServer = createAndStartServer(); 64 | CreateClients createClients = new CreateClients().invoke(); 65 | Service[] services = createClients.getServices(); 66 | Socket[] sockets = createClients.getSockets(); 67 | 68 | for (Service service2 : services) { 69 | for (int j = 0; j < 100; j++) { 70 | assertEquals(j, service2.inc()); 71 | } 72 | service2.reset(); 73 | } 74 | 75 | for (Service service1 : services) { 76 | assertEquals("hello dude", service1.hello("dude")); 77 | } 78 | 79 | stopClients(sockets); 80 | streamServer.stop(); 81 | } 82 | 83 | private void stopClients(Socket[] sockets) throws IOException { 84 | for (Socket socket : sockets) { 85 | socket.close(); 86 | } 87 | } 88 | 89 | @Test 90 | public void testStopWhileClientsWorking() throws Exception { 91 | 92 | StreamServer streamServer = createAndStartServer(); 93 | Socket socket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 94 | final Service service1 = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), Service.class, jsonRpcClient, socket); 95 | 96 | Thread t = new Thread(new Runnable() { 97 | @Override 98 | public void run() { 99 | // noinspection InfiniteLoopStatement 100 | while (true) { 101 | service1.inc(); 102 | } 103 | 104 | } 105 | }); 106 | t.start(); 107 | 108 | while (service.val < 10) { 109 | Thread.yield(); 110 | } 111 | streamServer.stop(); 112 | } 113 | 114 | @Test 115 | public void testClientDisconnectsCausingExceptionOnServer() throws Exception { 116 | StreamServer streamServer = createAndStartServer(); 117 | final Socket socket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 118 | final Service service1 = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), Service.class, jsonRpcClient, socket); 119 | Thread t = new Thread(new Runnable() { 120 | @Override 121 | public void run() { 122 | while (true) { 123 | if (service1.inc() > 5) { 124 | try { 125 | socket.close(); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | return; 130 | } 131 | } 132 | } 133 | }); 134 | 135 | Thread.sleep(1000); 136 | assertEquals(1, streamServer.getNumberOfConnections()); 137 | Server server = streamServer.getServers().iterator().next(); 138 | assertNotNull(server); 139 | t.start(); 140 | 141 | while (streamServer.getNumberOfConnections() > 0) { 142 | Thread.yield(); 143 | } 144 | assertEquals(0, server.getNumberOfErrors()); 145 | streamServer.stop(); 146 | } 147 | 148 | // @Test 149 | // Separating invoke() and readResponse() calls #20 150 | // this just isn't going to work with jackson 151 | @SuppressWarnings("unused") 152 | public void testMultipleClientCallsBeforeReadResponse() throws Throwable { 153 | StreamServer streamServer = createAndStartServer(); 154 | Socket socket = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 155 | 156 | InputStream ips = socket.getInputStream(); 157 | OutputStream ops = socket.getOutputStream(); 158 | 159 | for (int i = 0; i < 10; i++) { 160 | jsonRpcClient.invoke("inc", null, ops); 161 | } 162 | for (int i = 0; i < 10; i++) { 163 | Integer value = jsonRpcClient.readResponse(Integer.class, ips); 164 | assertEquals(i, value.intValue()); 165 | } 166 | socket.close(); 167 | while (streamServer.getNumberOfConnections() > 0) { 168 | Thread.yield(); 169 | } 170 | streamServer.stop(); 171 | } 172 | 173 | @SuppressWarnings("WeakerAccess") 174 | public interface Service { 175 | String hello(String whatever); 176 | 177 | int inc(); 178 | 179 | void reset(); 180 | } 181 | 182 | @SuppressWarnings("WeakerAccess") 183 | public static class ServiceImpl implements Service { 184 | 185 | private int val; 186 | 187 | public String hello(String whatever) { 188 | logger.info("server: hello({})", whatever); 189 | return "hello " + whatever; 190 | } 191 | 192 | public int inc() { 193 | logger.info("server: inc():", val); 194 | return val++; 195 | } 196 | 197 | public void reset() { 198 | val = 0; 199 | } 200 | 201 | } 202 | 203 | private class CreateClients { 204 | private Service[] services; 205 | private Socket[] sockets; 206 | 207 | Service[] getServices() { 208 | return services; 209 | } 210 | 211 | Socket[] getSockets() { 212 | return sockets; 213 | } 214 | 215 | CreateClients invoke() throws IOException { 216 | services = new Service[5]; 217 | sockets = new Socket[5]; 218 | for (int i = 0; i < services.length; i++) { 219 | sockets[i] = new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 220 | services[i] = ProxyUtil.createClientProxy(this.getClass().getClassLoader(), Service.class, jsonRpcClient, sockets[i]); 221 | } 222 | return this; 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/jsonrpc4j/server/JsonRpcServerBatchTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.server; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.ArrayNode; 5 | import com.googlecode.jsonrpc4j.JsonRpcServer; 6 | import com.googlecode.jsonrpc4j.server.JsonRpcServerAnnotatedParamTest.ServiceInterfaceWithParamNameAnnotation; 7 | import com.googlecode.jsonrpc4j.util.Util; 8 | 9 | import org.easymock.EasyMock; 10 | import org.easymock.Mock; 11 | import org.easymock.MockType; 12 | import org.junit.Test; 13 | import org.springframework.mock.web.MockHttpServletRequest; 14 | import org.springframework.mock.web.MockHttpServletResponse; 15 | import org.springframework.util.StreamUtils; 16 | 17 | import javax.servlet.http.HttpServletResponse; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.Locale; 23 | import java.util.UUID; 24 | 25 | import static com.googlecode.jsonrpc4j.ErrorResolver.JsonError.METHOD_PARAMS_INVALID; 26 | import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.*; 27 | import static com.googlecode.jsonrpc4j.server.JsonRpcServerAnnotatedParamTest.METHOD_WITH_DIFFERENT_TYPES; 28 | import static com.googlecode.jsonrpc4j.util.Util.*; 29 | import static org.junit.Assert.*; 30 | 31 | public abstract class JsonRpcServerBatchTest { 32 | @Mock(type = MockType.NICE) 33 | protected JsonRpcServerTest.ServiceInterface mockService; 34 | protected JsonRpcServer jsonRpcServer; 35 | 36 | @Test 37 | public void parallelBatchProcessingTest() throws Exception { 38 | EasyMock.expect(mockService.testMethod("Parameter1")).andReturn("Result1"); 39 | EasyMock.expect(mockService.testMethod("Parameter2")).andReturn("Result2"); 40 | EasyMock.replay(mockService); 41 | 42 | InputStream inputStream = multiMessageOfStream( 43 | messageWithListParams(1, "testMethod", "Parameter1"), 44 | messageWithListParams(2, "testMethod", "Parameter2")); 45 | 46 | MockHttpServletResponse response = handleRequest(inputStream); 47 | 48 | assertEquals(HttpServletResponse.SC_OK, response.getStatus()); 49 | JsonNode answer = decodeAnswer(toByteArrayOutputStream(response.getContentAsByteArray())); 50 | assertTrue(answer instanceof ArrayNode); 51 | assertEquals("Result1", getFromArrayWithId(answer, 1).get(RESULT).asText()); 52 | assertEquals("Result2", getFromArrayWithId(answer, 2).get(RESULT).asText()); 53 | } 54 | 55 | @Test 56 | public void parallelBatchProcessingBulkErrorTest() throws Exception { 57 | EasyMock.expect(mockService.testMethod("Parameter1")).andThrow(new RuntimeException("Error")); 58 | EasyMock.expect(mockService.testMethod("Parameter2")).andReturn("Result2"); 59 | EasyMock.replay(mockService); 60 | 61 | InputStream inputStream = multiMessageOfStream( 62 | messageWithListParams(1, "testMethod", "Parameter1"), 63 | messageWithListParams(2, "testMethod", "Parameter2")); 64 | 65 | MockHttpServletResponse response = handleRequest(inputStream); 66 | 67 | assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); 68 | JsonNode answer = decodeAnswer(toByteArrayOutputStream(response.getContentAsByteArray())); 69 | assertTrue(answer instanceof ArrayNode); 70 | assertEquals("Error", getFromArrayWithId(answer, 1).get(ERROR).get(DATA).get(ERROR_MESSAGE).asText()); 71 | assertEquals("Result2", getFromArrayWithId(answer, 2).get(RESULT).asText()); 72 | } 73 | 74 | @Test 75 | public void parallelBatchWithInvalidRequestInsideShouldPreserveJsonRpcId() throws Exception { 76 | ServiceInterfaceWithParamNameAnnotation mockServiceWithParams = 77 | EasyMock.strictMock(ServiceInterfaceWithParamNameAnnotation.class); 78 | 79 | jsonRpcServer = new JsonRpcServer( 80 | Util.mapper, 81 | mockServiceWithParams, 82 | ServiceInterfaceWithParamNameAnnotation.class 83 | ); 84 | 85 | final String validResult = "passResult"; 86 | final String validResultOne = validResult + "1"; 87 | final String validResultThree = validResult + "3"; 88 | 89 | EasyMock 90 | .expect( 91 | mockServiceWithParams.methodWithDifferentTypes( 92 | EasyMock.anyBoolean(), 93 | EasyMock.anyDouble(), 94 | EasyMock.anyObject(UUID.class) 95 | ) 96 | ) 97 | .andReturn(validResultOne); 98 | EasyMock 99 | .expect( 100 | mockServiceWithParams.methodWithDifferentTypes( 101 | EasyMock.anyBoolean(), 102 | EasyMock.anyDouble(), 103 | EasyMock.anyObject(UUID.class) 104 | ) 105 | ) 106 | .andReturn(validResultThree); 107 | EasyMock.replay(mockServiceWithParams); 108 | 109 | final List validParams = Arrays.asList(true, 3.14, UUID.randomUUID()); 110 | final String invalidBoolean = "truth"; 111 | 112 | InputStream inputStream = multiMessageOfStream( 113 | messageOfStream(1, METHOD_WITH_DIFFERENT_TYPES, validParams), 114 | messageOfStream( 115 | 2, 116 | METHOD_WITH_DIFFERENT_TYPES, 117 | Arrays.asList(invalidBoolean, 3.14, UUID.randomUUID()) 118 | ), 119 | messageOfStream(3, METHOD_WITH_DIFFERENT_TYPES, validParams) 120 | ); 121 | 122 | MockHttpServletResponse response = handleRequest(inputStream); 123 | 124 | EasyMock.verify(mockServiceWithParams); 125 | 126 | assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); 127 | JsonNode answer = decodeAnswer(toByteArrayOutputStream(response.getContentAsByteArray())); 128 | assertTrue(answer instanceof ArrayNode); 129 | 130 | assertEquals(validResultOne, getFromArrayWithId(answer, 1).get(RESULT).asText()); 131 | assertEquals(validResultThree, getFromArrayWithId(answer, 3).get(RESULT).asText()); 132 | 133 | JsonNode secondResponse = getFromArrayWithId(answer, 2); 134 | JsonNode responseId = secondResponse.get(ID); 135 | assertNotNull(responseId); 136 | assertTrue(responseId.isInt()); 137 | assertEquals(2, responseId.asInt()); 138 | 139 | JsonNode error = secondResponse.get(ERROR); 140 | assertNotNull(error); 141 | 142 | assertEquals(METHOD_PARAMS_INVALID.code, errorCode(error).asInt()); 143 | 144 | String errorMessage = error.get(ERROR_MESSAGE).asText(); 145 | assertTrue(errorMessage.toLowerCase(Locale.ROOT).contains("index")); 146 | assertTrue(errorMessage.contains("0")); 147 | } 148 | 149 | private static MockHttpServletRequest createRequest(InputStream inputStream) throws IOException { 150 | MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test-post"); 151 | request.setContent(StreamUtils.copyToByteArray(inputStream)); 152 | return request; 153 | } 154 | 155 | private MockHttpServletResponse handleRequest(InputStream inputStream) throws IOException { 156 | MockHttpServletRequest request = createRequest(inputStream); 157 | MockHttpServletResponse response = new MockHttpServletResponse(); 158 | jsonRpcServer.handle(request, response); 159 | return response; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/jsonrpc4j/spring/rest/MappingJacksonRPC2HttpMessageConverter.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.jsonrpc4j.spring.rest; 2 | 3 | import com.fasterxml.jackson.core.JsonEncoding; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.JavaType; 6 | import com.fasterxml.jackson.databind.JsonNode; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.node.ObjectNode; 9 | import org.springframework.http.HttpInputMessage; 10 | import org.springframework.http.HttpOutputMessage; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.http.converter.AbstractHttpMessageConverter; 13 | import org.springframework.http.converter.HttpMessageNotReadableException; 14 | import org.springframework.http.converter.HttpMessageNotWritableException; 15 | import org.springframework.util.Assert; 16 | 17 | import java.io.IOException; 18 | import java.nio.charset.Charset; 19 | import java.nio.charset.StandardCharsets; 20 | 21 | /** 22 | * JSON-RPC Message converter for Spring RestTemplate 23 | */ 24 | @SuppressWarnings({"WeakerAccess", "unused"}) 25 | class MappingJacksonRPC2HttpMessageConverter extends AbstractHttpMessageConverter { 26 | 27 | private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; 28 | public static final MediaType APPLICATION_JSON_RPC = new MediaType("application", "json-rpc", DEFAULT_CHARSET); 29 | 30 | private ObjectMapper objectMapper; 31 | 32 | private boolean prefixJson = false; 33 | 34 | /** 35 | * Construct a new {@code BindingJacksonHttpMessageConverter}. 36 | */ 37 | public MappingJacksonRPC2HttpMessageConverter() { 38 | super(APPLICATION_JSON_RPC); 39 | objectMapper = new ObjectMapper(); 40 | } 41 | 42 | /** 43 | * Construct a new {@code BindingJacksonHttpMessageConverter}. 44 | * 45 | * @param objectMapper the object mapper for this view 46 | */ 47 | public MappingJacksonRPC2HttpMessageConverter(ObjectMapper objectMapper) { 48 | super(APPLICATION_JSON_RPC); 49 | Assert.notNull(objectMapper, "ObjectMapper must not be null"); 50 | this.objectMapper = objectMapper; 51 | } 52 | 53 | /** 54 | * Return the underlying {@code ObjectMapper} for this view. 55 | * 56 | * @return the object mapper for this view 57 | */ 58 | public ObjectMapper getObjectMapper() { 59 | return this.objectMapper; 60 | } 61 | 62 | /** 63 | * Set the {@code ObjectMapper} for this view. If not set, a default 64 | * {@link ObjectMapper#ObjectMapper() ObjectMapper} is used. 65 | *

66 | * Setting a custom-configured {@code ObjectMapper} is one way to take further control of the JSON serialization 67 | * process. For example, an extended {@code org.codehaus.jackson.map.SerializerFactory} can be configured that 68 | * provides custom serializers for specific types. The other option for refining the serialization process is to use 69 | * Jackson's provided annotations on the types to be serialized, in which case a custom-configured ObjectMapper is 70 | * unnecessary. 71 | * 72 | * @param objectMapper the object mapper for this view 73 | */ 74 | public void setObjectMapper(ObjectMapper objectMapper) { 75 | Assert.notNull(objectMapper, "ObjectMapper must not be null"); 76 | this.objectMapper = objectMapper; 77 | } 78 | 79 | /** 80 | * Indicate whether the JSON output by this view should be prefixed with "{} &&". Default is false. 81 | *

82 | * Prefixing the JSON string in this manner is used to help prevent JSON Hijacking. The prefix renders the string 83 | * syntactically invalid as a script so that it cannot be hijacked. This prefix does not affect the evaluation of 84 | * JSON, but if JSON validation is performed on the string, the prefix would need to be ignored. 85 | * 86 | * @param prefixJson whether the JSON should be prefixed 87 | */ 88 | public void setPrefixJson(boolean prefixJson) { 89 | this.prefixJson = prefixJson; 90 | } 91 | 92 | @Override 93 | public boolean canRead(Class clazz, MediaType mediaType) { 94 | 95 | if (!JsonNode.class.isAssignableFrom(clazz)) { 96 | return false; 97 | } 98 | 99 | if (mediaType == null) { 100 | return true; 101 | } 102 | 103 | for (MediaType supportedMediaType : getSupportedMediaTypes()) { 104 | // we can't read multipart 105 | if (supportedMediaType.includes(mediaType)) { 106 | return true; 107 | } 108 | } 109 | return false; 110 | 111 | } 112 | 113 | @Override 114 | public boolean canWrite(Class clazz, MediaType mediaType) { 115 | 116 | if (!ObjectNode.class.isAssignableFrom(clazz)) { 117 | return false; 118 | } 119 | 120 | if (mediaType == null || MediaType.ALL.equals(mediaType)) { 121 | return true; 122 | } 123 | 124 | for (MediaType supportedMediaType : getSupportedMediaTypes()) { 125 | if (supportedMediaType.isCompatibleWith(mediaType)) { 126 | return true; 127 | } 128 | } 129 | 130 | return false; 131 | } 132 | 133 | @Override 134 | protected boolean supports(Class clazz) { 135 | // should not be called, since we override canRead/Write instead 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | @Override 140 | protected Object readInternal(Class clazz, HttpInputMessage inputMessage) 141 | throws HttpMessageNotReadableException { 142 | 143 | JavaType javaType = getJavaType(clazz); 144 | try { 145 | return this.objectMapper.readValue(inputMessage.getBody(), javaType); 146 | } catch (IOException ex) { 147 | throw new HttpMessageNotReadableException( 148 | "Could not read JSON: " + ex.getMessage(), 149 | ex, 150 | inputMessage 151 | ); 152 | } 153 | } 154 | 155 | @Override 156 | protected void writeInternal(Object object, HttpOutputMessage outputMessage) 157 | throws IOException, HttpMessageNotWritableException { 158 | 159 | final JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType()); 160 | final JsonGenerator jsonGenerator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding); 161 | try { 162 | if (this.prefixJson) { 163 | jsonGenerator.writeRaw("{} && "); 164 | } 165 | this.objectMapper.writeValue(jsonGenerator, object); 166 | } catch (IOException ex) { 167 | throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 168 | } 169 | } 170 | 171 | /** 172 | * Determine the JSON encoding to use for the given content type. 173 | * 174 | * @param contentType the media type as requested by the caller 175 | * @return the JSON encoding to use (never null) 176 | */ 177 | private JsonEncoding getJsonEncoding(MediaType contentType) { 178 | if (contentType != null && contentType.getCharset() != null) { 179 | Charset charset = contentType.getCharset(); 180 | for (JsonEncoding encoding : JsonEncoding.values()) { 181 | if (charset.name().equals(encoding.getJavaName())) { 182 | return encoding; 183 | } 184 | } 185 | } 186 | return JsonEncoding.UTF8; 187 | } 188 | 189 | /** 190 | * Return the Jackson {@link JavaType} for the specified class. 191 | *

192 | * The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)}, but this can be 193 | * overridden in subclasses, to allow for custom generic collection handling. For instance: 194 | *

195 | 	 * protected JavaType getJavaType(Class<?> clazz) { if (List.class.isAssignableFrom(clazz)) { return
196 | 	 * objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class); } else { return
197 | 	 * super.getJavaType(clazz); } }
198 | 	 * 
199 | * 200 | * @param javaClass the class to return the java type for 201 | * @return the java type 202 | */ 203 | private JavaType getJavaType(Class javaClass) { 204 | return objectMapper.constructType(javaClass); 205 | } 206 | 207 | } 208 | --------------------------------------------------------------------------------