From 2687b29c4626d1d09462acb81838c64d64a716ac Mon Sep 17 00:00:00 2001 From: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:18:50 +0300 Subject: [PATCH 1/4] Pull properties from org.springframework.remoting.support.RemoteExporter down into AbstractJsonServiceExporter RemoteExporter is deprecated and is now removed from Spring Framework v6+. It was used only to provide service and serviceInterface properties and some related methods. User defined Java services are registered in the Spring MVC using BeanNameUrlHandlerMapping, and RemoteExporter provided only some base functionality for accessing service class and interface. This functionality may be pulled down to AbstractJsonServiceExporter, and this preserves compatibility in existing applications, which use service and serviceInterface properties. Signed-off-by: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> --- .../AbstractCompositeJsonServiceExporter.java | 2 +- .../spring/AbstractJsonServiceExporter.java | 112 ++++++++++++++++-- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractCompositeJsonServiceExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractCompositeJsonServiceExporter.java index 8d2fb6cd..50cfd59c 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractCompositeJsonServiceExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractCompositeJsonServiceExporter.java @@ -93,7 +93,7 @@ public final void afterPropertiesSet() * * @throws Exception on error */ - void exportService() + protected void exportService() throws Exception { // no-op } diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractJsonServiceExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractJsonServiceExporter.java index ba145534..e9977117 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractJsonServiceExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/AbstractJsonServiceExporter.java @@ -2,23 +2,25 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.googlecode.jsonrpc4j.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; -import org.springframework.remoting.support.RemoteExporter; +import org.springframework.util.ClassUtils; import java.util.List; import java.util.concurrent.ExecutorService; /** - * {@link RemoteExporter} that exports services using Json - * according to the JSON-RPC proposal specified at: - * - * http://groups.google.com/group/json-rpc. + * Exports user defined services using JSON-RPC protocol */ @SuppressWarnings("unused") -abstract class AbstractJsonServiceExporter extends RemoteExporter implements InitializingBean, ApplicationContextAware { +abstract class AbstractJsonServiceExporter implements InitializingBean, ApplicationContextAware { + private static final Logger logger = LoggerFactory.getLogger(AbstractJsonServiceExporter.class); private ObjectMapper objectMapper; private JsonRpcServer jsonRpcServer; @@ -36,6 +38,8 @@ abstract class AbstractJsonServiceExporter extends RemoteExporter implements Ini private List interceptorList; private ExecutorService batchExecutorService = null; private long parallelBatchProcessingTimeout; + private Object service; + private Class serviceInterface; /** * {@inheritDoc} @@ -49,7 +53,7 @@ public void afterPropertiesSet() throws Exception { try { objectMapper = BeanFactoryUtils.beanOfTypeIncludingAncestors(applicationContext, ObjectMapper.class); } catch (Exception e) { - logger.debug(e); + logger.debug("Failed to obtain objectMapper from application context", e); } } if (objectMapper == null) { @@ -93,7 +97,7 @@ public void afterPropertiesSet() throws Exception { * * @throws Exception on error */ - void exportService() + protected void exportService() throws Exception { // no-op } @@ -214,4 +218,96 @@ public void setBatchExecutorService(ExecutorService batchExecutorService) { public void setParallelBatchProcessingTimeout(long parallelBatchProcessingTimeout) { this.parallelBatchProcessingTimeout = parallelBatchProcessingTimeout; } + + /** + * Set the service to export. + * Typically populated via a bean reference. + */ + public void setService(Object service) { + this.service = service; + } + + /** + * Return the service to export. + */ + public Object getService() { + return this.service; + } + + /** + * Set the interface of the service to export. + * The interface must be suitable for the particular service and remoting strategy. + */ + public void setServiceInterface(Class serviceInterface) { + if (serviceInterface == null) { + throw new IllegalArgumentException("'serviceInterface' must not be null"); + } + if (!serviceInterface.isInterface()) { + throw new IllegalArgumentException("'serviceInterface' must be an interface"); + } + this.serviceInterface = serviceInterface; + } + + /** + * Return the interface of the service to export. + */ + public Class getServiceInterface() { + return this.serviceInterface; + } + + + /** + * Check whether a service reference has been set, + * and whether it matches the specified service. + * @see #setServiceInterface + * @see #setService + */ + protected void checkServiceInterface() throws IllegalArgumentException { + Class serviceInterface = getServiceInterface(); + if (serviceInterface == null) { + throw new IllegalArgumentException("Property 'serviceInterface' is required"); + } + + Object service = getService(); + if (service instanceof String) { + throw new IllegalArgumentException( + "Service [" + service + "] is a String rather than an actual service reference:" + + " Have you accidentally specified the service bean name as value " + + " instead of as reference?" + ); + } + if (!serviceInterface.isInstance(service)) { + throw new IllegalArgumentException( + "Service interface [" + serviceInterface.getName() + + "] needs to be implemented by service [" + service + "] of class [" + + service.getClass().getName() + "]" + ); + } + } + + + /** + * Get a proxy for the given service object, implementing the specified + * service interface. + *

Used to export a proxy that does not expose any internals but just + * a specific interface intended for remote access. + * + * @return the proxy + * @see #setServiceInterface + * @see #setService + */ + protected Object getProxyForService() { + Object targetService = getService(); + if (targetService == null) { + throw new IllegalArgumentException("Property 'service' is required"); + } + checkServiceInterface(); + + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.addInterface(getServiceInterface()); + proxyFactory.setTarget(targetService); + proxyFactory.setOpaque(true); + + return proxyFactory.getProxy(ClassUtils.getDefaultClassLoader()); + } } From f7df43c44a5bfd40ec12952d317db7cd9e7c2049 Mon Sep 17 00:00:00 2001 From: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:32:14 +0300 Subject: [PATCH 2/4] Remove TraceInterceptor support from AutoJsonRpcServiceImplExporter and mark access methods as deprected RemoteExporter is deprecated and has been removed from Spring Framework starting from v6. This change removes registerTraceInterceptor parameter registration in RemoteExporter. Methods are marked as @Deprecated to preserve compile-time compatibility. Signed-off-by: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> --- .../spring/AutoJsonRpcServiceExporter.java | 13 ++++++------- .../spring/AutoJsonRpcServiceImplExporter.java | 17 ++++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceExporter.java index 1e27c798..83e9c7a9 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceExporter.java @@ -44,7 +44,6 @@ public class AutoJsonRpcServiceExporter implements BeanFactoryPostProcessor { private ObjectMapper objectMapper; private ErrorResolver errorResolver = null; - private Boolean registerTraceInterceptor; private boolean backwardsCompatible = true; private boolean rethrowExceptions = false; private boolean allowExtraParams = false; @@ -141,10 +140,6 @@ private void registerServiceProxy(DefaultListableBeanFactory defaultListableBean builder.addPropertyValue("invocationListener", invocationListener); } - if (registerTraceInterceptor != null) { - builder.addPropertyValue("registerTraceInterceptor", registerTraceInterceptor); - } - if (httpStatusCodeProvider != null) { builder.addPropertyValue("httpStatusCodeProvider", httpStatusCodeProvider); } @@ -225,12 +220,16 @@ public void setAllowLessParams(boolean allowLessParams) { } /** - * See {@link org.springframework.remoting.support.RemoteExporter#setRegisterTraceInterceptor(boolean)} + * See {@code org.springframework.remoting.support.RemoteExporter#setRegisterTraceInterceptor(boolean)} + *

+ * Note: this method is deprecated and marked for removal. + * {@code RemoteExporter} and {@code TraceInterceptor-s} are no longer supported. * * @param registerTraceInterceptor the registerTraceInterceptor value to set */ + @Deprecated public void setRegisterTraceInterceptor(boolean registerTraceInterceptor) { - this.registerTraceInterceptor = registerTraceInterceptor; + // NOOP } /** diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceImplExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceImplExporter.java index db9cad1a..ad355395 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceImplExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/AutoJsonRpcServiceImplExporter.java @@ -50,7 +50,6 @@ public class AutoJsonRpcServiceImplExporter implements BeanFactoryPostProcessor private ObjectMapper objectMapper; private ErrorResolver errorResolver = null; - private Boolean registerTraceInterceptor; private boolean backwardsCompatible = true; private boolean rethrowExceptions = false; private boolean allowExtraParams = false; @@ -181,11 +180,7 @@ private void registerServiceProxy(DefaultListableBeanFactory defaultListableBean if (invocationListener != null) { builder.addPropertyValue("invocationListener", invocationListener); } - - if (registerTraceInterceptor != null) { - builder.addPropertyValue("registerTraceInterceptor", registerTraceInterceptor); - } - + if (httpStatusCodeProvider != null) { builder.addPropertyValue("httpStatusCodeProvider", httpStatusCodeProvider); } @@ -280,14 +275,18 @@ public void setAllowExtraParams(boolean allowExtraParams) { public void setAllowLessParams(boolean allowLessParams) { this.allowLessParams = allowLessParams; } - + /** - * See {@link org.springframework.remoting.support.RemoteExporter#setRegisterTraceInterceptor(boolean)} + * See {@code org.springframework.remoting.support.RemoteExporter#setRegisterTraceInterceptor(boolean)} + *

+ * Note: this method is deprecated and marked for removal. + * {@code RemoteExporter} and {@code TraceInterceptor-s} are no longer supported. * * @param registerTraceInterceptor the registerTraceInterceptor value to set */ + @Deprecated public void setRegisterTraceInterceptor(boolean registerTraceInterceptor) { - this.registerTraceInterceptor = registerTraceInterceptor; + // NOOP } /** From 0ca46778f1740a7ed73f89b9b50f982145556b8c Mon Sep 17 00:00:00 2001 From: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> Date: Tue, 13 Jun 2023 11:34:13 +0300 Subject: [PATCH 3/4] Remove references of RemoteExporter from the documentation and javadocs Signed-off-by: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> --- README.md | 8 +++++--- .../googlecode/jsonrpc4j/spring/JsonServiceExporter.java | 5 +---- .../jsonrpc4j/spring/JsonStreamServiceExporter.java | 6 +----- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ea800db1..d49dbf2b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ JSON-RPC). * HTTP Server (`HttpServletRequest` \ `HttpServletResponse`) * Portlet Server (`ResourceRequest` \ `ResourceResponse`) * Socket Server (`StreamServer`) - * Integration with the Spring Framework (`RemoteExporter`) + * Integration with the Spring Framework * Streaming client * HTTP client * Dynamic client proxies @@ -66,7 +66,7 @@ that take `InputStream`s and `OutputStream`s. Also in the library is a `JsonRpc which extends the `JsonRpcClient` to add HTTP support. ## Spring Framework -jsonrpc4j provides a `RemoteExporter` to expose java services as JSON-RPC over HTTP without +jsonrpc4j provides support for exposing java services as JSON-RPC over HTTP without requiring any additional work on the part of the programmer. The following example explains how to use the `JsonServiceExporter` within the Spring Framework. @@ -113,7 +113,9 @@ public class UserServiceImpl } ``` -Configure your service in spring as you would any other RemoteExporter: +Configure your service in Spring as you would do it for any other Beans, +and then add a reference of your service Bean into `JsonServiceExporter` +by specifying the `service` and `serviceInterface` properties: ```xml diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporter.java index 7bc2ab53..e43cd1ac 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporter.java @@ -9,10 +9,7 @@ import java.io.IOException; /** - * {@link HttpRequestHandler} that exports services using Json - * according to the JSON-RPC proposal specified at: - * - * http://groups.google.com/group/json-rpc. + * {@link HttpRequestHandler} that exports user services using JSON-RPC over HTTP protocol */ public class JsonServiceExporter extends AbstractJsonServiceExporter implements HttpRequestHandler { diff --git a/src/main/java/com/googlecode/jsonrpc4j/spring/JsonStreamServiceExporter.java b/src/main/java/com/googlecode/jsonrpc4j/spring/JsonStreamServiceExporter.java index ae62351e..7469c469 100644 --- a/src/main/java/com/googlecode/jsonrpc4j/spring/JsonStreamServiceExporter.java +++ b/src/main/java/com/googlecode/jsonrpc4j/spring/JsonStreamServiceExporter.java @@ -11,11 +11,7 @@ /** - * {@link org.springframework.remoting.support.RemoteExporter RemoteExporter} - * that exports services using Json according to the JSON-RPC proposal specified - * at: - * - * http://groups.google.com/group/json-rpc. + * Exports user defined services as streaming server, which provides JSON-RPC over sockets. */ @SuppressWarnings("unused") public class JsonStreamServiceExporter extends AbstractJsonServiceExporter implements DisposableBean { From 42eef515235d5662d0ff328db704949936b48537 Mon Sep 17 00:00:00 2001 From: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:28:46 +0300 Subject: [PATCH 4/4] Add integration test for checking JsonServiceExporter configuration in the application context Adds same Spring Context XML configuration as described in the README and verifies that beans are properly registered. Signed-off-by: cyb3r4nt <104218001+cyb3r4nt@users.noreply.github.com> --- .../JsonServiceExporterIntegrationTest.java | 53 +++++++++++++++++++ .../resources/serverApplicationContextC.xml | 14 +++++ 2 files changed, 67 insertions(+) create mode 100644 src/test/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporterIntegrationTest.java create mode 100644 src/test/resources/serverApplicationContextC.xml diff --git a/src/test/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporterIntegrationTest.java b/src/test/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporterIntegrationTest.java new file mode 100644 index 00000000..dfd36248 --- /dev/null +++ b/src/test/java/com/googlecode/jsonrpc4j/spring/JsonServiceExporterIntegrationTest.java @@ -0,0 +1,53 @@ +package com.googlecode.jsonrpc4j.spring; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.googlecode.jsonrpc4j.spring.service.Service; +import com.googlecode.jsonrpc4j.spring.service.ServiceImpl; + +import static org.junit.Assert.*; + +/** + * This test ensures that {@link com.googlecode.jsonrpc4j.spring.JsonServiceExporter} bean is + * constructed according to Spring Framework configuration. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:serverApplicationContextC.xml") +public class JsonServiceExporterIntegrationTest { + + private static final String BEAN_NAME_AND_URL_PATH = "/UserService.json"; + + @Autowired + private ApplicationContext applicationContext; + + @Test + public void testExportedService() { + assertNotNull(applicationContext); + + // check that the bean was only exported on the configured path. + { + Object bean = applicationContext.getBean(BEAN_NAME_AND_URL_PATH); + assertEquals(JsonServiceExporter.class, bean.getClass()); + + String[] names = applicationContext.getBeanNamesForType(JsonServiceExporter.class); + assertNotNull(names); + assertEquals(1, names.length); + assertEquals(BEAN_NAME_AND_URL_PATH, names[0]); + } + + // check that service classes were also successfully configured in the context. + + { + Service service = applicationContext.getBean(Service.class); + assertTrue(service instanceof ServiceImpl); + + ServiceImpl serviceImpl = applicationContext.getBean(ServiceImpl.class); + assertNotNull(serviceImpl); + } + } +} diff --git a/src/test/resources/serverApplicationContextC.xml b/src/test/resources/serverApplicationContextC.xml new file mode 100644 index 00000000..ed2c0156 --- /dev/null +++ b/src/test/resources/serverApplicationContextC.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + +