From f65bb820d837f567c9ba6189fc8897788c063039 Mon Sep 17 00:00:00 2001 From: Sashir Estela Date: Sat, 23 Mar 2024 05:34:32 -0500 Subject: [PATCH] feature: #2542 Dynamic proxy pattern (#2814) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #2542 First commit * #2542 Fixing comments * #2542 Refactoring code * #2542 Update Readme * #2542 Add mockito dependencies * #2542 Add unit test * #2542 Fixes for testing * #2542 Fixing typos * #2542 Fixing SonarLint issues * #2542 Fixing code review --------- Co-authored-by: Sashir Estela Co-authored-by: Ilkka Seppälä --- dynamic-proxy/README.md | 354 ++++++++++++++++++ dynamic-proxy/etc/dynamic-proxy-classes.png | Bin 0 -> 25689 bytes dynamic-proxy/etc/dynamic-proxy-classes.puml | 35 ++ dynamic-proxy/etc/dynamic-proxy-sequence.png | Bin 0 -> 54913 bytes dynamic-proxy/etc/dynamic-proxy-sequence.puml | 63 ++++ dynamic-proxy/etc/dynamic-proxy.urm.puml | 96 +++++ dynamic-proxy/pom.xml | 72 ++++ .../java/com/iluwatar/dynamicproxy/Album.java | 46 +++ .../dynamicproxy/AlbumInvocationHandler.java | 61 +++ .../iluwatar/dynamicproxy/AlbumService.java | 87 +++++ .../java/com/iluwatar/dynamicproxy/App.java | 109 ++++++ .../dynamicproxy/tinyrestclient/JsonUtil.java | 98 +++++ .../tinyrestclient/TinyRestClient.java | 182 +++++++++ .../tinyrestclient/annotation/Body.java | 39 ++ .../tinyrestclient/annotation/Delete.java | 45 +++ .../tinyrestclient/annotation/Get.java | 45 +++ .../tinyrestclient/annotation/Http.java | 38 ++ .../tinyrestclient/annotation/Path.java | 44 +++ .../tinyrestclient/annotation/Post.java | 45 +++ .../tinyrestclient/annotation/Put.java | 45 +++ .../com/iluwatar/dynamicproxy/AppTest.java | 37 ++ pom.xml | 1 + 22 files changed, 1542 insertions(+) create mode 100644 dynamic-proxy/README.md create mode 100644 dynamic-proxy/etc/dynamic-proxy-classes.png create mode 100644 dynamic-proxy/etc/dynamic-proxy-classes.puml create mode 100644 dynamic-proxy/etc/dynamic-proxy-sequence.png create mode 100644 dynamic-proxy/etc/dynamic-proxy-sequence.puml create mode 100644 dynamic-proxy/etc/dynamic-proxy.urm.puml create mode 100644 dynamic-proxy/pom.xml create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java create mode 100644 dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java create mode 100644 dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java diff --git a/dynamic-proxy/README.md b/dynamic-proxy/README.md new file mode 100644 index 000000000..21976ddeb --- /dev/null +++ b/dynamic-proxy/README.md @@ -0,0 +1,354 @@ +--- +title: Dynamic Proxy +category: Structural +language: en +tag: +- Generic +--- + +## Intent +Dynamic proxy is a Java mechanism that allows developers to create a proxy instance for interfaces at runtime. It is primarily used for intercepting method calls, enabling developers to add additional processing around the actual method invocation. + +## Explanation + +### Real-world example +Mockito, a popular Java mocking framework, employs dynamic proxy to create mock objects for testing. Mock objects mimic the behavior of real objects, allowing developers to isolate components and verify interactions in unit tests. + +Consider a scenario where a service class depends on an external component, such as a database access object (DAO). Instead of interacting with a real DAO in a test, Mockito can dynamically generate a proxy, intercepting method invocations and returning predefined values. This enables focused unit testing without the need for a real database connection. + +### In plain words +Dynamic proxy is a specialized form of proxy in Java, serving as a flexible and dynamic method to intercept and manipulate method calls. By utilizing dynamic proxies, developers can implement additional functionalities without modifying the original class code. + +### Wikipedia says +A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance's invocation handler, and they are encoded with a _java.lang.reflect.Method_ object identifying the method that was invoked and an array of type _Object_ containing the arguments. + +### Programmatic Example +This example allow us to hit the public fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. + +The call to _Proxy.newProxyInstance_ creates a new dynamic proxy for the _AlbumService_ interface and sets the _AlbumInvocationHandler_ class as the handler to intercept all the interface's methods. Everytime that we call an _AlbumService_'s method, the handler's method `invoke` will be call automatically, and it will pass all the method's metadata and arguments to other specialized class - _TinyRestClient_ - to prepare the Rest API call accordingly. + +![Sequence diagram](./etc/dynamic-proxy-sequence.png "Dynamic Proxy sequence diagram") + +In this demo, the Dynamic Proxy pattern help us to run business logic through an interface without an explicit implementation of that interface and supported on the Java Reflection approach. + +```java +public class App { + + private static final Logger logger = LoggerFactory.getLogger(App.class); + + static final String REST_API_URL = "https://jsonplaceholder.typicode.com"; + + private String baseUrl; + private HttpClient httpClient; + private AlbumService albumServiceProxy; + + /** + * Class constructor. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public App(String baseUrl, HttpClient httpClient) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; + } + + /** + * Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler. + */ + public void createDynamicProxy() { + AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient); + + albumServiceProxy = (AlbumService) Proxy.newProxyInstance( + App.class.getClassLoader(), new Class[] { AlbumService.class }, albumInvocationHandler); + } + + /** + * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods + * and receive the responses from the Rest API. + */ + public void callMethods() { + int albumId = 17; + int userId = 3; + + var albums = albumServiceProxy.readAlbums(); + albums.forEach(album -> logger.info("{}", album)); + + var album = albumServiceProxy.readAlbum(albumId); + logger.info("{}", album); + + var newAlbum = albumServiceProxy.createAlbum(Album.builder() + .title("Big World").userId(userId).build()); + logger.info("{}", newAlbum); + + var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder() + .title("Green Valley").userId(userId).build()); + logger.info("{}", editAlbum); + + var removedAlbum = albumServiceProxy.deleteAlbum(albumId); + logger.info("{}", removedAlbum); + } + + /** + * Application entry point. + * + * @param args External arguments to be passed. Optional. + */ + public static void main(String[] args) { + App app = new App(App.REST_API_URL, HttpClient.newHttpClient()); + app.createDynamicProxy(); + app.callMethods(); + } + +} +``` + +Declaration of the AlbumService interface. Annotations were used to provide additional information such as: http method (Get, Post, Put, Delete), endpoint's url, path parameters, body parameters, among others. + +```java +public interface AlbumService { + + /** + * Get a list of albums from an endpoint. + * + * @return List of albums' data. + */ + @Get("/albums") + List readAlbums(); + + /** + * Get a specific album from an endpoint. + * + * @param albumId Album's id to search for. + * @return Album's data. + */ + @Get("/albums/{albumId}") + Album readAlbum(@Path("albumId") Integer albumId); + + /** + * Creates a new album. + * + * @param album Album's data to be created. + * @return New album's data. + */ + @Post("/albums") + Album createAlbum(@Body Album album); + + /** + * Updates an existing album. + * + * @param albumId Album's id to be modified. + * @param album New album's data. + * @return Updated album's data. + */ + @Put("/albums/{albumId}") + Album updateAlbum(@Path("albumId") Integer albumId, @Body Album album); + + /** + * Deletes an album. + * + * @param albumId Album's id to be deleted. + * @return Empty album. + */ + @Delete("/albums/{albumId}") + Album deleteAlbum(@Path("albumId") Integer albumId); + +} +``` + +Declaration of the AlbumInvocationHandler class whose method _invoke_ will be called automatically every time that an AlbumService's method is called. The call processing is delegated to the _TinyRestClient_. + +```java +public class AlbumInvocationHandler implements InvocationHandler { + + private static final Logger logger = LoggerFactory.getLogger(AlbumInvocationHandler.class); + + private TinyRestClient restClient; + + /** + * Class constructor. It instantiates a TinyRestClient object. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { + this.restClient = new TinyRestClient(baseUrl, httpClient); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + logger.info("===== Calling the method {}.{}()", + method.getDeclaringClass().getSimpleName(), method.getName()); + + return restClient.send(method, args); + } + +} +``` + +The TinyRestClient class handle all the http communication with a Rest API. It is supported by the built-in component HttpClient. This class is an optional part of the pattern. + +```java +public class TinyRestClient { + + private String baseUrl; + private HttpClient httpClient; + + /** + * Class constructor. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public TinyRestClient(String baseUrl, HttpClient httpClient) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; + } + + /** + * Creates a http communication to request and receive data from an endpoint. + * + * @param method Interface's method which is annotated with a http method. + * @param args Method's arguments passed in the call. + * @return Response from the endpoint. + * @throws IOException Exception thrown when any fail happens in the call. + * @throws InterruptedException Exception thrown when call is interrupted. + */ + public Object send(Method method, Object[] args) throws IOException, InterruptedException { + var httpMethodAnnotation = getHttpMethodAnnotation(method.getDeclaredAnnotations()); + if (httpMethodAnnotation == null) { + return null; + } + var httpMethodName = httpMethodAnnotation.annotationType().getSimpleName().toUpperCase(); + var url = baseUrl + buildUrl(method, args, httpMethodAnnotation); + var bodyPublisher = buildBodyPublisher(method, args); + var httpRequest = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .method(httpMethodName, bodyPublisher) + .build(); + var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + return getResponse(method, httpResponse); + } + + private String buildUrl(Method method, Object[] args, Annotation httpMethodAnnotation) { + var url = annotationValue(httpMethodAnnotation); + if (url == null) { + return ""; + } + var index = 0; + for (var parameter : method.getParameters()) { + var pathAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Path.class); + if (pathAnnotation != null) { + var pathParam = "{" + annotationValue(pathAnnotation) + "}"; + var pathValue = args[index].toString(); + url = url.replace(pathParam, pathValue); + } + index++; + } + return url; + } + + private HttpRequest.BodyPublisher buildBodyPublisher(Method method, Object[] args) { + var index = 0; + for (var parameter : method.getParameters()) { + var bodyAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Body.class); + if (bodyAnnotation != null) { + var body = JsonUtil.objectToJson(args[index]); + return HttpRequest.BodyPublishers.ofString(body); + } + index++; + } + return HttpRequest.BodyPublishers.noBody(); + } + + private Object getResponse(Method method, HttpResponse httpResponse) { + var rawData = httpResponse.body(); + var returnType = method.getGenericReturnType(); + if (returnType instanceof ParameterizedType) { + Class responseClass = (Class) (((ParameterizedType) returnType) + .getActualTypeArguments()[0]); + return JsonUtil.jsonToList(rawData, responseClass); + } else { + Class responseClass = method.getReturnType(); + return JsonUtil.jsonToObject(rawData, responseClass); + } + } + + private Annotation getHttpMethodAnnotation(Annotation[] annotations) { + return Arrays.stream(annotations) + .filter(annot -> annot.annotationType().isAnnotationPresent(HttpMethod.class)) + .findFirst().orElse(null); + } + + private Annotation getAnnotationOf(Annotation[] annotations, Class clazz) { + return Arrays.stream(annotations) + .filter(annot -> annot.annotationType().equals(clazz)) + .findFirst().orElse(null); + } + + private String annotationValue(Annotation annotation) { + var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods()) + .filter(methodAnnot -> methodAnnot.getName().equals("value")) + .findFirst().orElse(null); + if (valueMethod == null) { + return null; + } + Object result; + try { + result = valueMethod.invoke(annotation, (Object[]) null); + } catch (Exception e) { + result = null; + } + return (result instanceof String strResult ? strResult : null); + } +} +``` + +## Class diagram +![Class diagram](./etc/dynamic-proxy-classes.png "Dynamic Proxy class diagram") + +## Applicability +Dynamic proxy should be used when you need to augment or enhance your current functionality without modifying your current code. Some examples of that usage could be: + +- _Logging and Monitoring_: By creating a logging proxy, developers can intercept method invocations and log relevant information like method names, parameters, and execution times. + +- _Security and Access Control_: Developers can create proxies that enforce security policies, restricting access to certain methods based on user roles or permissions. + +- _Aspect-Oriented Programming_: Dynamic proxies facilitate the application of transversal aspects to methods without changing the original code. + +## Tutorials +- [Dynamic Proxies in Java - CodeGym](https://codegym.cc/groups/posts/208-dynamic-proxies) +- [Introduction To Java Dynamic Proxy - Xperti](https://xperti.io/blogs/java-dynamic-proxies-introduction/) + +## Known uses +Many frameworks and libraries use dynamic proxy to implement their functionalities: +- [Spring Framework](https://docs.spring.io/spring-framework/reference/core/aop.html), for aspect oriented programming. +- [Hibernate](https://hibernate.org/orm/), for data lazy loading. +- [Mockito](https://site.mockito.org/), for mocking objects in testing. +- [Cleverclient](https://github.com/sashirestela/cleverclient), for calling http endpoints through annotated interfaces. + +## Consequences + +### Pros +- **Increased flexibility in code**: Dynamic proxies in Java offer a high degree of flexibility, allowing developers to create versatile and adaptable applications. By using dynamic proxies, software +engineers can modify the behavior of methods at runtime, which is particularly useful in scenarios where the behavior of classes needs to be augmented or manipulated without altering their source code. +This flexibility is crucial in developing applications that require dynamic response to varying conditions or need to integrate with systems where interfaces may change over time. +- **Simplifying complex operations**: Dynamic proxies excel in simplifying complex operations, particularly in the areas of cross-cutting concerns such as logging, transaction management, and security. By intercepting method calls, dynamic proxies can uniformly apply certain operations across various methods and classes, thereby reducing the need for repetitive code. This capability is particularly beneficial in large-scale applications where such cross-cutting concerns are prevalent. For example, adding logging or authorization checks across multiple methods becomes a matter of implementing these features once in an invocation handler, rather than modifying each method individually. +- **Enhancing code maintainability**: Maintainability is a key advantage of using dynamic proxies. They promote cleaner and more organized code by separating the core business logic from cross-cutting concerns. This separation of concerns not only makes the codebase more understandable but also easier to test and debug. When the business logic is decoupled from aspects like logging or transaction handling, any changes in these areas do not impact the core functionality of the application. As a result, applications become more robust and easier to maintain and update, which is crucial in the fast-paced environment of software development where requirements and technologies are constantly evolving. + +### Cons +- **Performance overhead**: The use of reflection and method invocation through proxies can introduce latency, especially in performance-critical applications. This overhead might be negligible in most cases, but it becomes significant in scenarios with high-frequency method calls. +- **Complexity of debugging**: Since dynamic proxies introduce an additional layer of abstraction, tracing and debugging issues can be more challenging. It can be difficult to trace the flow of execution through proxies, especially when multiple proxies are involved. +- **Limited to interface-based programming**: They can only proxy interfaces, not classes. This limitation requires careful design considerations, particularly in situations where class-based proxies would be more appropriate. +- **Higher level of expertise**: Developers are normally not a fan of “magic code” — code that works in a non-transparent or overly complex manner. Those unfamiliar with the proxy pattern or reflection might find the codebase more complex to understand and maintain, potentially leading to errors or misuse of the feature. This complexity can be perceived as a form of “magic” that obscures the underlying process, making the code less intuitive and more challenging to debug or extend. Therefore, while dynamic proxies are powerful, their use should be approached with caution and a thorough understanding of their inner workings. + +## Related patterns +- [Proxy](https://java-design-patterns.com/patterns/proxy) + +## Credits +- [Dynamic Proxies in Java - Baeldung](https://www.baeldung.com/java-dynamic-proxies) +- [Dynamic Proxy Classes - Oracle](https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html) +- [Intro To Java Dynamic Proxies - KapreSoft](https://www.kapresoft.com/java/2023/12/27/intro-to-java-proxies.html) +- [Exploring the Depths of Dynamic Proxy in Java: A Comprehensive Guide](https://naveen-metta.medium.com/exploring-the-depths-of-dynamic-proxy-in-java-a-comprehensive-guide-f34fb45b38a3) diff --git a/dynamic-proxy/etc/dynamic-proxy-classes.png b/dynamic-proxy/etc/dynamic-proxy-classes.png new file mode 100644 index 0000000000000000000000000000000000000000..86f5a7db7900bd83cb013034a371850052099bb6 GIT binary patch literal 25689 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 brZ=IIIwV2*!n>3Pzx~Zf-|SEk#x>^OV1R~* literal 0 HcmV?d00001 diff --git a/dynamic-proxy/etc/dynamic-proxy-classes.puml b/dynamic-proxy/etc/dynamic-proxy-classes.puml new file mode 100644 index 000000000..119f1dc11 --- /dev/null +++ b/dynamic-proxy/etc/dynamic-proxy-classes.puml @@ -0,0 +1,35 @@ +@startuml + +class A as "App" { +} + +interface I as "MyInterface" { + someMethod(args) + otherMethods(args) +} + +class P as "Proxy" <> { + {static} DynamicProxy newProxyInstance(\n classLoader,\n interfaces,\n invocationHandler\n) +} + +class D as "DynamicProxy" { + someMethod(args) + otherMethods(args) +} + +class H as "InvocationHandler" { + invoke(proxy, method, args) +} + +class X as "AuxiliarProcessor" { + process(method, args) +} + +A -r-> D + +I <|.. D : implements + +D -r-> H : delegate +H -r-> X : (optional) + +@enduml diff --git a/dynamic-proxy/etc/dynamic-proxy-sequence.png b/dynamic-proxy/etc/dynamic-proxy-sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..9a139b1c8ffe44dc9eaf9333e9310c4bd1950f93 GIT binary patch literal 54913 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 +participant DynamicProxy as D <> +participant InvocationHandler as H +participant AuxiliarProcessor as R + +== Create Dynamic Proxy == + +create H +A -> H : new(args) +activate H + +create R +H -> R : new(args) +activate R + +R --> H : processor +deactivate R + +H --> A : invocationHandler +deactivate H + +||| + +A -> P : newProxyInstance(\n classLoader,\n new Class[]{MyInterface.class},\n invocationHandler\n) +activate P + +create D +P -> D : new +activate D + +D --> P : dynamicProxy +deactivate D + +P --> A : dynamicProxy +deactivate P + +== Call Interface's Method == + +A -> D : someMethod(args) +activate D + +D -> H : invoke(proxy, method, args) +activate H + +H -> R : process(method, args) +activate R + +R -> R : parse method's\nmetadata +R -> R : execute some\nprocessing + +R --> H : response +deactivate R + +H --> D : response +deactivate H + +D --> A : response +deactivate D + +@enduml diff --git a/dynamic-proxy/etc/dynamic-proxy.urm.puml b/dynamic-proxy/etc/dynamic-proxy.urm.puml new file mode 100644 index 000000000..5db603aae --- /dev/null +++ b/dynamic-proxy/etc/dynamic-proxy.urm.puml @@ -0,0 +1,96 @@ +@startuml +package com.iluwatar.dynamicproxy.tinyrestclient.annotation { + interface Body { + } + interface Delete { + + value() : String {abstract} + } + interface Get { + + value() : String {abstract} + } + interface HttpMethod { + } + interface Path { + + value() : String {abstract} + } + interface Post { + + value() : String {abstract} + } + interface Put { + + value() : String {abstract} + } +} +package com.iluwatar.dynamicproxy.tinyrestclient { + class JsonUtil { + - objectMapper : ObjectMapper {static} + - JsonUtil() + + jsonToList(json : String, clazz : Class) : List {static} + + jsonToObject(json : String, clazz : Class) : T {static} + + objectToJson(object : T) : String {static} + } + class TinyRestClient { + - baseUrl : String + + TinyRestClient(baseUrl : String) + - annotationValue(annotation : Annotation) : String + - buildBodyPublisher(method : Method, args : Object[]) : BodyPublisher + - buildUrl(method : Method, args : Object[], httpMethodAnnotation : Annotation) : String + - getAnnotationOf(annotations : Annotation[], clazz : Class) : Annotation + - getHttpMethodAnnotation(annotations : Annotation[]) : Annotation + - getResponse(method : Method, httpResponse : HttpResponse) : Object + + send(method : Method, args : Object[]) : Object + } +} +package com.iluwatar.dynamicproxy { + class Album { + - id : Integer + - title : String + - userId : Integer + + Album() + + Album(id : Integer, title : String, userId : Integer) + + builder() : AlbumBuilder {static} + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Integer + + getTitle() : String + + getUserId() : Integer + + hashCode() : int + + setId(id : Integer) + + setTitle(title : String) + + setUserId(userId : Integer) + + toString() : String + } + class AlbumBuilder { + - id : Integer + - title : String + - userId : Integer + ~ AlbumBuilder() + + build() : Album + + id(id : Integer) : AlbumBuilder + + title(title : String) : AlbumBuilder + + toString() : String + + userId(userId : Integer) : AlbumBuilder + } + class AlbumInvocationHandler { + - restClient : TinyRestClient + + AlbumInvocationHandler(baseUrl : String) + + invoke(proxy : Object, method : Method, args : Object[]) : Object + } + interface AlbumService { + + createAlbum(Album) : Album {abstract} + + deleteAlbum(Integer) : Album {abstract} + + readAlbum(Integer) : Album {abstract} + + readAlbums() : List {abstract} + + updateAlbum(Integer, Album) : Album {abstract} + } + class App { + ~ REST_API_URL : String {static} + - albumServiceProxy : AlbumService + + App() + + callMethods() + + createDynamicProxy() + + main(args : String[]) {static} + } +} +AlbumInvocationHandler --> "-restClient" TinyRestClient +App --> "-albumServiceProxy" AlbumService +@enduml \ No newline at end of file diff --git a/dynamic-proxy/pom.xml b/dynamic-proxy/pom.xml new file mode 100644 index 000000000..0ec69f0bc --- /dev/null +++ b/dynamic-proxy/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + dynamic-proxy + + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + + + org.springframework + spring-web + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.dynamicproxy.App + + + + + + + + + diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java new file mode 100644 index 000000000..1a4efc606 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * This class represents an endpoint resource that + * we are going to interchange with a Rest API server. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor(force = true) +@Builder +public class Album { + + private Integer id; + private String title; + private Integer userId; + +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java new file mode 100644 index 000000000..e53485ae0 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy; + +import com.iluwatar.dynamicproxy.tinyrestclient.TinyRestClient; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.http.HttpClient; +import lombok.extern.slf4j.Slf4j; + +/** + * Class whose method 'invoke' will be called every time that an interface's method is called. + * That interface is linked to this class by the Proxy class. + */ +@Slf4j +public class AlbumInvocationHandler implements InvocationHandler { + + private TinyRestClient restClient; + + /** + * Class constructor. It instantiates a TinyRestClient object. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { + this.restClient = new TinyRestClient(baseUrl, httpClient); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + LOGGER.info("===== Calling the method {}.{}()", + method.getDeclaringClass().getSimpleName(), method.getName()); + + return restClient.send(method, args); + } + +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java new file mode 100644 index 000000000..c0975b6e6 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java @@ -0,0 +1,87 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy; + +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Body; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Delete; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Get; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Path; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Post; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Put; +import java.util.List; + +/** + * Every method in this interface is annotated with the necessary metadata to represents an endpoint + * that we can call to communicate with a host server which is serving a resource by Rest API. + * This interface is focused in the resource Album. + */ +public interface AlbumService { + + /** + * Get a list of albums from an endpoint. + * + * @return List of albums' data. + */ + @Get("/albums") + List readAlbums(); + + /** + * Get a specific album from an endpoint. + * + * @param albumId Album's id to search for. + * @return Album's data. + */ + @Get("/albums/{albumId}") + Album readAlbum(@Path("albumId") Integer albumId); + + /** + * Creates a new album. + * + * @param album Album's data to be created. + * @return New album's data. + */ + @Post("/albums") + Album createAlbum(@Body Album album); + + /** + * Updates an existing album. + * + * @param albumId Album's id to be modified. + * @param album New album's data. + * @return Updated album's data. + */ + @Put("/albums/{albumId}") + Album updateAlbum(@Path("albumId") Integer albumId, @Body Album album); + + /** + * Deletes an album. + * + * @param albumId Album's id to be deleted. + * @return Empty album. + */ + @Delete("/albums/{albumId}") + Album deleteAlbum(@Path("albumId") Integer albumId); + +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java new file mode 100644 index 000000000..61118694d --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java @@ -0,0 +1,109 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy; + +import java.lang.reflect.Proxy; +import java.net.http.HttpClient; +import lombok.extern.slf4j.Slf4j; + +/** + * Application to demonstrate the Dynamic Proxy pattern. This application allow us to hit the public + * fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. + * The call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and + * sets the AlbumInvocationHandler class as the handler to intercept all the interface's methods. + * Everytime that we call an AlbumService's method, the handler's method "invoke" will be call + * automatically, and it will pass all the method's metadata and arguments to other specialized + * class - TinyRestClient - to prepare the Rest API call accordingly. + * In this demo, the Dynamic Proxy pattern help us to run business logic through interfaces without + * an explicit implementation of the interfaces and supported on the Java Reflection approach. + */ +@Slf4j +public class App { + + static final String REST_API_URL = "https://jsonplaceholder.typicode.com"; + + private String baseUrl; + private HttpClient httpClient; + private AlbumService albumServiceProxy; + + /** + * Class constructor. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public App(String baseUrl, HttpClient httpClient) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; + } + + /** + * Application entry point. + * + * @param args External arguments to be passed. Optional. + */ + public static void main(String[] args) { + App app = new App(App.REST_API_URL, HttpClient.newHttpClient()); + app.createDynamicProxy(); + app.callMethods(); + } + + /** + * Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler. + */ + public void createDynamicProxy() { + AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient); + + albumServiceProxy = (AlbumService) Proxy.newProxyInstance( + App.class.getClassLoader(), new Class[]{AlbumService.class}, albumInvocationHandler); + } + + /** + * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods + * and receive the responses from the Rest API. + */ + public void callMethods() { + int albumId = 17; + int userId = 3; + + var albums = albumServiceProxy.readAlbums(); + albums.forEach(album -> LOGGER.info("{}", album)); + + var album = albumServiceProxy.readAlbum(albumId); + LOGGER.info("{}", album); + + var newAlbum = albumServiceProxy.createAlbum(Album.builder() + .title("Big World").userId(userId).build()); + LOGGER.info("{}", newAlbum); + + var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder() + .title("Green Valley").userId(userId).build()); + LOGGER.info("{}", editAlbum); + + var removedAlbum = albumServiceProxy.deleteAlbum(albumId); + LOGGER.info("{}", removedAlbum); + } + +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java new file mode 100644 index 000000000..f76fe88ec --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java @@ -0,0 +1,98 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; + +/** + * Utility class to handle Json operations. + */ +@Slf4j +public class JsonUtil { + + private static ObjectMapper objectMapper = new ObjectMapper(); + + private JsonUtil() { + } + + /** + * Convert an object to a Json string representation. + * + * @param object Object to convert. + * @param Object's class. + * @return Json string. + */ + public static String objectToJson(T object) { + try { + return objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + LOGGER.error("Cannot convert the object " + object + " to Json.", e); + return null; + } + } + + /** + * Convert a Json string to an object of a class. + * + * @param json Json string to convert. + * @param clazz Object's class. + * @param Object's generic class. + * @return Object. + */ + public static T jsonToObject(String json, Class clazz) { + try { + return objectMapper.readValue(json, clazz); + } catch (IOException e) { + LOGGER.error("Cannot convert the Json " + json + " to class " + clazz.getName() + ".", e); + return null; + } + } + + /** + * Convert a Json string to a List of objects of a class. + * + * @param json Json string to convert. + * @param clazz Object's class. + * @param Object's generic class. + * @return List of objects. + */ + public static List jsonToList(String json, Class clazz) { + try { + CollectionType listType = objectMapper.getTypeFactory() + .constructCollectionType(ArrayList.class, clazz); + return objectMapper.reader().forType(listType).readValue(json); + } catch (JsonProcessingException e) { + LOGGER.error("Cannot convert the Json " + json + " to List of " + clazz.getName() + ".", e); + return List.of(); + } + } + +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java new file mode 100644 index 000000000..854791a72 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java @@ -0,0 +1,182 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient; + +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Body; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Http; +import com.iluwatar.dynamicproxy.tinyrestclient.annotation.Path; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.util.UriUtils; + +/** + * Class to handle all the http communication with a Rest API. + * It is supported by the HttpClient Java library. + */ +@Slf4j +public class TinyRestClient { + + private static Map httpAnnotationByMethod = new HashMap<>(); + + private String baseUrl; + private HttpClient httpClient; + + /** + * Class constructor. + * + * @param baseUrl Root url for endpoints. + * @param httpClient Handle the http communication. + */ + public TinyRestClient(String baseUrl, HttpClient httpClient) { + this.baseUrl = baseUrl; + this.httpClient = httpClient; + } + + /** + * Creates a http communication to request and receive data from an endpoint. + * + * @param method Interface's method which is annotated with a http method. + * @param args Method's arguments passed in the call. + * @return Response from the endpoint. + * @throws IOException Exception thrown when any fail happens in the call. + * @throws InterruptedException Exception thrown when call is interrupted. + */ + public Object send(Method method, Object[] args) throws IOException, InterruptedException { + var httpAnnotation = getHttpAnnotation(method); + if (httpAnnotation == null) { + return null; + } + var httpAnnotationName = httpAnnotation.annotationType().getSimpleName().toUpperCase(); + var url = baseUrl + buildUrl(method, args, httpAnnotation); + var bodyPublisher = buildBodyPublisher(method, args); + var httpRequest = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .method(httpAnnotationName, bodyPublisher) + .build(); + var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + var statusCode = httpResponse.statusCode(); + if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) { + var errorDetail = httpResponse.body(); + LOGGER.error("Error from server: " + errorDetail); + return null; + } + return getResponse(method, httpResponse); + } + + private String buildUrl(Method method, Object[] args, Annotation httpMethodAnnotation) { + var url = annotationValue(httpMethodAnnotation); + if (url == null) { + return ""; + } + var index = 0; + for (var parameter : method.getParameters()) { + var pathAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Path.class); + if (pathAnnotation != null) { + var pathParam = "{" + annotationValue(pathAnnotation) + "}"; + var pathValue = UriUtils.encodePath(args[index].toString(), StandardCharsets.UTF_8); + url = url.replace(pathParam, pathValue); + } + index++; + } + return url; + } + + private HttpRequest.BodyPublisher buildBodyPublisher(Method method, Object[] args) { + var index = 0; + for (var parameter : method.getParameters()) { + var bodyAnnotation = getAnnotationOf(parameter.getDeclaredAnnotations(), Body.class); + if (bodyAnnotation != null) { + var body = JsonUtil.objectToJson(args[index]); + return HttpRequest.BodyPublishers.ofString(body); + } + index++; + } + return HttpRequest.BodyPublishers.noBody(); + } + + private Object getResponse(Method method, HttpResponse httpResponse) { + var rawData = httpResponse.body(); + Type returnType = null; + try { + returnType = method.getGenericReturnType(); + } catch (Exception e) { + LOGGER.error("Cannot get the generic return type of the method " + method.getName() + "()"); + return null; + } + if (returnType instanceof ParameterizedType) { + Class responseClass = (Class) (((ParameterizedType) returnType) + .getActualTypeArguments()[0]); + return JsonUtil.jsonToList(rawData, responseClass); + } else { + Class responseClass = method.getReturnType(); + return JsonUtil.jsonToObject(rawData, responseClass); + } + } + + private Annotation getHttpAnnotation(Method method) { + return httpAnnotationByMethod.computeIfAbsent(method, m -> + Arrays.stream(m.getDeclaredAnnotations()) + .filter(annot -> annot.annotationType().isAnnotationPresent(Http.class)) + .findFirst().orElse(null)); + } + + private Annotation getAnnotationOf(Annotation[] annotations, Class clazz) { + return Arrays.stream(annotations) + .filter(annot -> annot.annotationType().equals(clazz)) + .findFirst().orElse(null); + } + + private String annotationValue(Annotation annotation) { + var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods()) + .filter(methodAnnot -> methodAnnot.getName().equals("value")) + .findFirst().orElse(null); + if (valueMethod == null) { + return null; + } + Object result; + try { + result = valueMethod.invoke(annotation, (Object[]) null); + } catch (Exception e) { + LOGGER.error("Cannot read the value " + annotation.annotationType().getSimpleName() + + "." + valueMethod.getName() + "()", e); + result = null; + } + return (result instanceof String strResult ? strResult : null); + } +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java new file mode 100644 index 000000000..1d91c0af1 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a method's parameter as a Body parameter. + * It is typically used on Post and Put http methods. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Body { +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java new file mode 100644 index 000000000..5f21457ab --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark an interface's method as a DELETE http method. + */ +@Http +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Delete { + /** + * Set the url for this http method. + * + * @return Url address. + */ + String value() default ""; +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java new file mode 100644 index 000000000..163a65ad9 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark an interface's method as a GET http method. + */ +@Http +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Get { + /** + * Set the url for this http method. + * + * @return Url address. + */ + String value() default ""; +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java new file mode 100644 index 000000000..beabcdb69 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark other annotations to be recognized as http methods. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +public @interface Http { +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java new file mode 100644 index 000000000..0f2bcd007 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java @@ -0,0 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a method's parameter as a Path parameter. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Path { + /** + * Path parameter to be replaced in the url. + * + * @return Path parameter. + */ + String value(); +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java new file mode 100644 index 000000000..9885389c4 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark an interface's method as a POST http method. + */ +@Http +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Post { + /** + * Set the url for this http method. + * + * @return Url address. + */ + String value() default ""; +} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java new file mode 100644 index 000000000..851303ed8 --- /dev/null +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy.tinyrestclient.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark an interface's method as a PUT http method. + */ +@Http +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Put { + /** + * Set the url for this http method. + * + * @return Url address. + */ + String value() default ""; +} diff --git a/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java new file mode 100644 index 000000000..72412ca5b --- /dev/null +++ b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dynamicproxy; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class AppTest { + + @Test + void shouldRunAppWithoutExceptions() { + assertDoesNotThrow(() -> App.main(null)); + } +} diff --git a/pom.xml b/pom.xml index 32cbbaa15..a9273da8e 100644 --- a/pom.xml +++ b/pom.xml @@ -213,6 +213,7 @@ health-check notification single-table-inheritance + dynamic-proxy gateway