├── .sdkmanrc ├── .gitignore ├── docs ├── design │ ├── use-case1.png │ ├── user-beans.png │ ├── user-beans10.png │ ├── user-beans11.png │ ├── user-beans12.png │ ├── user-beans13.png │ ├── user-beans14.png │ ├── user-beans15.png │ ├── user-beans16.png │ ├── user-beans2.png │ ├── user-beans3.png │ ├── user-beans4.png │ ├── user-beans5.png │ ├── user-beans6.png │ ├── user-beans7.png │ ├── user-beans8.png │ ├── user-beans9.png │ └── spring-petclinic.png ├── graph-combo.json └── index.html ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .vscode └── settings.json ├── examples ├── README.md ├── hello-world-reactive │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.properties │ │ │ │ ├── static │ │ │ │ │ └── index.html │ │ │ │ └── banner.txt │ │ │ └── java │ │ │ │ └── info │ │ │ │ └── jab │ │ │ │ └── ms │ │ │ │ └── MainApplication.java │ │ └── test │ │ │ └── java │ │ │ └── info │ │ │ └── jab │ │ │ └── ms │ │ │ └── MainApplicationTests.java │ └── pom.xml ├── hello-world-servlet │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.properties │ │ │ │ ├── static │ │ │ │ │ └── index.html │ │ │ │ └── banner.txt │ │ │ └── java │ │ │ │ └── info │ │ │ │ └── jab │ │ │ │ └── ms │ │ │ │ ├── MyService.java │ │ │ │ ├── MainApplication.java │ │ │ │ └── MyController.java │ │ └── test │ │ │ └── java │ │ │ └── info │ │ │ └── jab │ │ │ └── ms │ │ │ ├── MainApplicationTests.java │ │ │ └── MainApplicationE2eTests.java │ └── pom.xml └── pom.xml ├── spring-boot-starter-user-beans ├── src │ ├── main │ │ ├── resources │ │ │ ├── io │ │ │ │ └── github │ │ │ │ │ └── jabrena │ │ │ │ │ └── userbeans │ │ │ │ │ └── application.properties │ │ │ ├── META-INF │ │ │ │ └── spring │ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── static │ │ │ │ ├── details.html │ │ │ │ └── graph.html │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── jabrena │ │ │ └── userbeans │ │ │ ├── ChatGPTConfig.java │ │ │ ├── WebDocumentReader.java │ │ │ ├── JavaClassExplanationService.java │ │ │ ├── UserBeansService.java │ │ │ ├── UserBeansEndpoint.java │ │ │ ├── ClasspathDependencyReader.java │ │ │ ├── ChatGPTProvider.java │ │ │ └── UserBeansGraphService.java │ └── test │ │ ├── resources │ │ └── __files │ │ │ ├── 401-incorrect-api-key.json │ │ │ ├── 400-max-content-reached.json │ │ │ └── 200-ok.json │ │ └── java │ │ └── io │ │ └── github │ │ └── jabrena │ │ ├── support │ │ ├── TestApplication.java │ │ └── SupportController.java │ │ └── userbeans │ │ ├── JavaClassExplanationServiceTests.java │ │ ├── UserBeansServiceTests.java │ │ ├── ChatGPTProviderTests.java │ │ ├── UserBeansEndpointsE2eTests.java │ │ └── UserBeansGraphServiceTests.java └── pom.xml ├── .editorconfig ├── .github └── workflows │ └── build.yaml ├── src └── checkstyle │ ├── checkstyle-suppressions.xml │ └── google_checks.xml ├── .devcontainer └── devcontainer.json ├── external-tests ├── README.md └── pom.xml ├── coverage-module └── pom.xml ├── mvnw.cmd ├── pom.xml ├── LICENSE ├── README.md └── mvnw /.sdkmanrc: -------------------------------------------------------------------------------- 1 | java=17.0.7-tem -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .idea 4 | .env 5 | .DS_Store 6 | external-tests/spring-petclinc/** 7 | local-m2 8 | -------------------------------------------------------------------------------- /docs/design/use-case1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/use-case1.png -------------------------------------------------------------------------------- /docs/design/user-beans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans.png -------------------------------------------------------------------------------- /docs/design/user-beans10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans10.png -------------------------------------------------------------------------------- /docs/design/user-beans11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans11.png -------------------------------------------------------------------------------- /docs/design/user-beans12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans12.png -------------------------------------------------------------------------------- /docs/design/user-beans13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans13.png -------------------------------------------------------------------------------- /docs/design/user-beans14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans14.png -------------------------------------------------------------------------------- /docs/design/user-beans15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans15.png -------------------------------------------------------------------------------- /docs/design/user-beans16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans16.png -------------------------------------------------------------------------------- /docs/design/user-beans2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans2.png -------------------------------------------------------------------------------- /docs/design/user-beans3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans3.png -------------------------------------------------------------------------------- /docs/design/user-beans4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans4.png -------------------------------------------------------------------------------- /docs/design/user-beans5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans5.png -------------------------------------------------------------------------------- /docs/design/user-beans6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans6.png -------------------------------------------------------------------------------- /docs/design/user-beans7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans7.png -------------------------------------------------------------------------------- /docs/design/user-beans8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans8.png -------------------------------------------------------------------------------- /docs/design/user-beans9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/user-beans9.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /docs/design/spring-petclinic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jabrena/spring-boot-user-beans/HEAD/docs/design/spring-petclinic.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic", 3 | "java.compile.nullAnalysis.mode": "automatic" 4 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Spring Boot CLI 4 | 5 | ``` 6 | sdk install springboot 7 | spring init -d=web,devtools --build=maven --force ./ 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.output.ansi.enabled=always 2 | server.http2.enabled=true 3 | management.endpoints.web.exposure.include=beans,userbeans 4 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.output.ansi.enabled=always 2 | server.http2.enabled=true 3 | management.endpoints.web.exposure.include=beans,userbeans 4 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/resources/io/github/jabrena/userbeans/application.properties: -------------------------------------------------------------------------------- 1 | userbeans.openapi.url=https://api.openai.com/v1/completions 2 | userbeans.openapi.model=text-davinci-003 3 | userbeans.openapi.max_tokens=3000 4 | userbeans.openapi.temperature=0 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.html,*.xml,*.java] 13 | indent_style = space 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/resources/__files/401-incorrect-api-key.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "message": "Incorrect API key provided: YOUR_API******ALUE. You can find your API key at https://platform.openai.com/account/api-keys.", 4 | "type": "invalid_request_error", 5 | "param": null, 6 | "code": "invalid_api_key" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/java/info/jab/ms/MyService.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class MyService { 7 | 8 | public record HelloWorld(String message) {} 9 | 10 | public HelloWorld getMessage() { 11 | return new HelloWorld("Hello World"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/resources/__files/400-max-content-reached.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "message": "This model's maximum context length is 4097 tokens, however you requested 4130 tokens (130 in your prompt; 4000 for the completion). Please reduce your prompt; or completion length.", 4 | "type": "invalid_request_error", 5 | "param": null, 6 | "code": null 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Graphs 6 | 7 | 8 |

Spring Boot User Beans

9 |

Graphs

10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Graphs 6 | 7 | 8 |

Spring Boot User Beans

9 |

Graphs

10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | io.github.jabrena.userbeans.UserBeansEndpoint 2 | io.github.jabrena.userbeans.UserBeansService 3 | io.github.jabrena.userbeans.JavaClassExplanationService 4 | io.github.jabrena.userbeans.UserBeansGraphService 5 | io.github.jabrena.userbeans.ChatGPTConfig 6 | io.github.jabrena.userbeans.ChatGPTProvider 7 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/src/main/java/info/jab/ms/MainApplication.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MainApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MainApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/java/info/jab/ms/MainApplication.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication(proxyBeanMethods = false) 7 | public class MainApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MainApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/ChatGPTConfig.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration(proxyBeanMethods = false) 8 | public class ChatGPTConfig { 9 | 10 | @Bean(name = "ChatGPTMapper") 11 | public ObjectMapper getMapper() { 12 | return new ObjectMapper(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | # # # # 3 | # # ###### # # #### # # # #### ##### # ##### 4 | # # # # # # # # # # # # # # # # # 5 | ####### ##### # # # # # # # # # # # # # # 6 | # # # # # # # # # # # # ##### # # # 7 | # # # # # # # # # # # # # # # # # 8 | # # ###### ###### ###### #### ## ## #### # # ###### ##### 9 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | # # # # 3 | # # ###### # # #### # # # #### ##### # ##### 4 | # # # # # # # # # # # # # # # # # 5 | ####### ##### # # # # # # # # # # # # # # 6 | # # # # # # # # # # # # ##### # # # 7 | # # # # # # # # # # # # # # # # # 8 | # # ###### ###### ###### #### ## ## #### # # ###### ##### 9 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/support/TestApplication.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.support; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * A spring boot application used to manage 8 | * the Spring Context for testing purposes. 9 | */ 10 | @SpringBootApplication(proxyBeanMethods = false) 11 | public class TestApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(TestApplication.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: CI Builds 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | 9 | jobs: 10 | run: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-java@v3 16 | with: 17 | distribution: 'temurin' # See 'Supported distributions' for available options 18 | java-version: '17' 19 | 20 | - name: Maven Verify 21 | run: | 22 | ./mvnw --batch-mode --no-transfer-progress -B verify sonar:sonar -Ppipelines 23 | -------------------------------------------------------------------------------- /src/checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/support/SupportController.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.support; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) 11 | public class SupportController { 12 | 13 | public record HelloWorld(String message) {} 14 | 15 | @GetMapping("demo") 16 | ResponseEntity getMessage() { 17 | return ResponseEntity.ok().body(new HelloWorld("Hello World")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/main/java/info/jab/ms/MyController.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import org.springframework.http.MediaType; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | @RestController 10 | @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) 11 | public class MyController { 12 | 13 | private final MyService myService; 14 | 15 | public MyController(MyService myService) { 16 | this.myService = myService; 17 | } 18 | 19 | @GetMapping("demo") 20 | ResponseEntity getMessage() { 21 | return ResponseEntity.ok().body(myService.getMessage()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/resources/__files/200-ok.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cmpl-7VKCjui16vTfI9roVyTRfZrfukHd2", 3 | "object": "text_completion", 4 | "created": 1687700189, 5 | "model": "text-davinci-003", 6 | "choices": [ 7 | { 8 | "text": "\n\nThe SimpleObservationRegistry class in the io.micrometer.observation package of the micrometer-observation-1.11.0.jar dependency is used to register and manage observations of metrics. It provides a simple API to register and unregister observations, as well as to query the registered observations. It also allows for the registration of custom metrics.", 9 | "index": 0, 10 | "logprobs": null, 11 | "finish_reason": "stop" 12 | } 13 | ], 14 | "usage": { 15 | "prompt_tokens": 54, 16 | "completion_tokens": 80, 17 | "total_tokens": 134 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | 8 | io.github.jabrena 9 | spring-boot-user-beans 10 | 0.3.0-SNAPSHOT 11 | 12 | 13 | io.github.jabrena 14 | examples 15 | 0.3.0-SNAPSHOT 16 | pom 17 | examples 18 | 19 | 20 | hello-world-servlet 21 | hello-world-reactive 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/src/test/java/info/jab/ms/MainApplicationTests.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.actuate.beans.BeansEndpoint; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | @SpringBootTest 11 | class MainApplicationTests { 12 | 13 | @Autowired 14 | private BeansEndpoint beansEndpoint; 15 | 16 | @Test 17 | void contextLoads() { 18 | assertThat(beansEndpoint).isNotNull(); 19 | } 20 | 21 | @Test 22 | void mainMethodShouldStartApplication() { 23 | //Given 24 | String[] args = {}; 25 | 26 | //When 27 | MainApplication.main(args); 28 | 29 | //Then 30 | assertThat(beansEndpoint).isNotNull(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/test/java/info/jab/ms/MainApplicationTests.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.actuate.beans.BeansEndpoint; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | @SpringBootTest 11 | class MainApplicationTests { 12 | 13 | @Autowired 14 | private BeansEndpoint beansEndpoint; 15 | 16 | @Test 17 | void contextLoads() { 18 | assertThat(beansEndpoint).isNotNull(); 19 | } 20 | 21 | @Test 22 | void mainMethodShouldStartApplication() { 23 | //Given 24 | String[] args = {}; 25 | 26 | //When 27 | MainApplication.main(args); 28 | 29 | //Then 30 | assertThat(beansEndpoint).isNotNull(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/graph-combo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dependency" : "jackson-databind-2.15.0.jar" 4 | }, 5 | { 6 | "dependency" : "jackson-module-parameter-names-2.15.0.jar" 7 | }, 8 | { 9 | "dependency" : "micrometer-core-1.11.0.jar" 10 | }, 11 | { 12 | "dependency" : "micrometer-observation-1.11.0.jar" 13 | }, 14 | { 15 | "dependency" : "spring-boot-3.1.0.jar" 16 | }, 17 | { 18 | "dependency" : "spring-boot-actuator-3.1.0.jar" 19 | }, 20 | { 21 | "dependency" : "spring-boot-actuator-autoconfigure-3.1.0.jar" 22 | }, 23 | { 24 | "dependency" : "spring-boot-autoconfigure-3.1.0.jar" 25 | }, 26 | { 27 | "dependency" : "spring-context-6.0.9.jar" 28 | }, 29 | { 30 | "dependency" : "spring-core-6.0.9.jar" 31 | }, 32 | { 33 | "dependency" : "spring-web-6.0.9.jar" 34 | }, 35 | { 36 | "dependency" : "spring-webmvc-6.0.9.jar" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.2/apache-maven-3.9.2-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/java 3 | { 4 | "name": "Java", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/java:0-17", 7 | "hostRequirements": { 8 | "cpus": 4 9 | }, 10 | "features": { 11 | "ghcr.io/devcontainers/features/java:1": { 12 | "version": "none", 13 | "installMaven": "true", 14 | "installGradle": "false" 15 | }, 16 | "ghcr.io/devcontainers/features/sshd:1": { 17 | "version": "latest" 18 | }, 19 | "ghcr.io/devcontainers/features/docker-in-docker:2": { 20 | "version": "latest", 21 | "dockerDashComposeVersion": "v1" 22 | } 23 | }, 24 | "customizations": { 25 | "vscode": { 26 | "extensions": [ 27 | "vscjava.vscode-java-pack", 28 | "vscjava.vscode-java-debug", 29 | "vscjava.vscode-maven", 30 | "mathiasfrohlich.kotlin", 31 | "vscjava.vscode-gradle", 32 | "vscjava.vscode-java-dependency", 33 | "vscjava.vscode-java-test", 34 | "vscjava.vscode-spring-boot-dashboard", 35 | "vscjava.vscode-spring-initializr", 36 | "redhat.java", 37 | "vmware.vscode-boot-dev-pack", 38 | "vmware.vscode-spring-boot", 39 | "ms-azuretools.vscode-docker", 40 | "redhat.vscode-xml", 41 | "redhat.vscode-yaml", 42 | "editorconfig.editorconfig" 43 | ] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /external-tests/README.md: -------------------------------------------------------------------------------- 1 | # External tests 2 | 3 | This project has been tested with the classic project Spring Petclinic. 4 | 5 | ``` 6 | git clone https://github.com/spring-projects/spring-petclinic.git 7 | ``` 8 | 9 | In the spring-petclinic, add the dependency: 10 | 11 | ```xml 12 | 13 | io.github.jabrena 14 | spring-boot-starter-user-beans 15 | 0.1.0-SNAPSHOT 16 | 17 | ``` 18 | 19 | And run from root pom.xml 20 | 21 | ``` 22 | ./mvnw spring-boot:run -pl external-tests/spring-petclinic -am 23 | ``` 24 | 25 | ``` 26 | |\ _,,,--,,_ 27 | /,`.-'`' ._ \-;;,_ 28 | _______ __|,4- ) )_ .;.(__`'-'__ ___ __ _ ___ _______ 29 | | | '---''(_/._)-'(_\_) | | | | | | | | | 30 | | _ | ___|_ _| | | | | |_| | | | __ _ _ 31 | | |_| | |___ | | | | | | | | | | \ \ \ \ 32 | | ___| ___| | | | _| |___| | _ | | _| \ \ \ \ 33 | | | | |___ | | | |_| | | | | | | |_ ) ) ) ) 34 | |___| |_______| |___| |_______|_______|___|_| |__|___|_______| / / / / 35 | ==================================================================/_/_/_/ 36 | 37 | :: Built with Spring Boot :: 3.1.1 38 | 39 | ``` 40 | 41 | Open the webbrowser with the following url: 42 | 43 | ``` 44 | open http://localhost:8080/actuator/userbeans 45 | ``` 46 | 47 | 48 | ## References 49 | 50 | - https://github.com/spring-projects/spring-petclinic/tree/main 51 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/WebDocumentReader.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.util.Objects; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class WebDocumentReader { 12 | 13 | private static final Logger logger = LoggerFactory.getLogger(WebDocumentReader.class); 14 | 15 | String readFromResources(String fileName) { 16 | String html = ""; 17 | try (InputStream ioStream = this.getClass().getClassLoader().getResourceAsStream(fileName)) { 18 | if (Objects.isNull(ioStream)) { 19 | logger.error(fileName + " is not found"); 20 | throw new IllegalArgumentException(fileName + " is not found"); 21 | } else { 22 | StringBuilder stringBuilder = new StringBuilder(); 23 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(ioStream)); 24 | 25 | String line; 26 | while ((line = bufferedReader.readLine()) != null) { 27 | stringBuilder.append(line); 28 | stringBuilder.append(System.lineSeparator()); // Add line separator if needed 29 | } 30 | 31 | html = stringBuilder.toString(); 32 | } 33 | } catch (IOException e) { 34 | logger.warn(e.getMessage(), e); 35 | } 36 | return html; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /external-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | 8 | info.jab 9 | spring-boot-user-beans 10 | 0.1.0-SNAPSHOT 11 | 12 | 13 | info.jab 14 | external-tests 15 | 0.1.0-SNAPSHOT 16 | pom 17 | external-tests 18 | 19 | 20 | spring-petclinic 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-checkstyle-plugin 28 | 3.1.1 29 | 30 | 31 | checkstyle 32 | verify 33 | 34 | check 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/src/test/java/info/jab/ms/MainApplicationE2eTests.java: -------------------------------------------------------------------------------- 1 | package info.jab.ms; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.web.client.TestRestTemplate; 9 | import org.springframework.boot.test.web.server.LocalServerPort; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.http.HttpStatusCode; 12 | import org.springframework.http.ResponseEntity; 13 | 14 | @SpringBootTest( 15 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 16 | properties = { "management.endpoints.web.exposure.include=beans,userbeans" } 17 | ) 18 | class MainApplicationE2eTests { 19 | 20 | @Autowired 21 | private TestRestTemplate restTemplate; 22 | 23 | @LocalServerPort 24 | int randomServerPort; 25 | 26 | @Test 27 | void shouldReturnTheUserBeans() throws Exception { 28 | //Given 29 | final String baseUrl = "http://localhost:" + randomServerPort + "/demo"; 30 | 31 | record ExpectedHelloWorld(String message) {} 32 | 33 | //When 34 | // @formatter:off 35 | ResponseEntity result = this.restTemplate.exchange( 36 | baseUrl, 37 | HttpMethod.GET, 38 | null, 39 | ExpectedHelloWorld.class); 40 | // @formatter:on 41 | 42 | //Then 43 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 44 | assertThat(result.getBody()).isNotNull(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /coverage-module/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.github.jabrena 8 | spring-boot-user-beans 9 | 0.3.0-SNAPSHOT 10 | 11 | 12 | io.github.jabrena 13 | coverage-module 14 | 0.3.0-SNAPSHOT 15 | coverage-module 16 | coverage-module 17 | jar 18 | 19 | 20 | 17 21 | 22 | 23 | 24 | 25 | io.github.jabrena 26 | spring-boot-starter-user-beans 27 | 0.3.0-SNAPSHOT 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.jacoco 35 | jacoco-maven-plugin 36 | 0.8.10 37 | 38 | 39 | jacoco-site-aggregate 40 | verify 41 | 42 | report-aggregate 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/JavaClassExplanationService.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class JavaClassExplanationService { 9 | 10 | private static final Logger logger = LoggerFactory.getLogger(JavaClassExplanationService.class); 11 | 12 | private final ChatGPTProvider chatGPTProvider; 13 | private final WebDocumentReader webDocumentReader; 14 | 15 | public JavaClassExplanationService(ChatGPTProvider chatGPTProvider) { 16 | this.chatGPTProvider = chatGPTProvider; 17 | this.webDocumentReader = new WebDocumentReader(); 18 | } 19 | 20 | // @formatter:off 21 | String generateDetailsWebDocument() { 22 | logger.info("Generating Web Document"); 23 | String fileName = "static/details.html"; 24 | return webDocumentReader.readFromResources(fileName); 25 | } 26 | 27 | // @formatter:on 28 | 29 | public record DetailsExplanation(String response) {} 30 | 31 | // @formatter:off 32 | public DetailsExplanation generateDetailsContent(String beanClass, String packageName, String dependency) { 33 | 34 | logger.info("Asking ChatGPT to explain a specific Spring Class"); 35 | 36 | String question = """ 37 | Can you create an article about the purpose 38 | of the Java class: %s 39 | with this package: %s 40 | included in this Dependency: %s 41 | which include the following sections: 42 | - Java class purpose 43 | - How that Java Class interact with the rest of Spring Ecosystem? 44 | - What is the benefit of that Java class in Spring? 45 | - Is it necessary to customize in some way by the developer? 46 | 47 | in HTML format? 48 | """; 49 | 50 | question = String.format(question, beanClass, packageName, dependency); 51 | return new DetailsExplanation(chatGPTProvider.getAnswer(question)); 52 | } 53 | // @formatter:on 54 | } 55 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/userbeans/JavaClassExplanationServiceTests.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.mockito.ArgumentMatchers.anyString; 5 | import static org.mockito.Mockito.when; 6 | 7 | import io.github.jabrena.support.TestApplication; 8 | import org.jsoup.Jsoup; 9 | import org.jsoup.nodes.Document; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.CsvSource; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | 17 | @SpringBootTest(classes = { TestApplication.class }, properties = { "management.endpoints.web.exposure.include=beans,userbeans" }) 18 | class JavaClassExplanationServiceTests { 19 | 20 | @MockBean 21 | private ChatGPTProvider chatGPTProvider; 22 | 23 | @Autowired 24 | private JavaClassExplanationService beanExplanationService; 25 | 26 | @Test 27 | void shouldReturnValidDetailsWebDocument() { 28 | //Given 29 | //When 30 | var html = beanExplanationService.generateDetailsWebDocument(); 31 | Document doc = Jsoup.parse(html); 32 | 33 | //Then 34 | assertThat(doc).isNotNull(); 35 | } 36 | 37 | @ParameterizedTest 38 | @CsvSource( 39 | { 40 | "X, Y, Z, Mocked Result 1", "A, B, C, Mocked Result 2", 41 | // Add more test cases as needed 42 | } 43 | ) 44 | void shouldProcessAnswerFromChatGPT(String input1, String input2, String input3, String expectedResult) { 45 | //Given 46 | //var expectedResult = "Mocked Result"; 47 | when(chatGPTProvider.getAnswer(anyString())).thenReturn(expectedResult); 48 | 49 | //When 50 | var result = beanExplanationService.generateDetailsContent(input1, input2, input3); 51 | 52 | //Then 53 | assertThat(result.response()).isEqualTo(expectedResult); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/userbeans/UserBeansServiceTests.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import io.github.jabrena.support.SupportController; 6 | import io.github.jabrena.support.TestApplication; 7 | import java.util.Comparator; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | @SpringBootTest( 13 | classes = { TestApplication.class, SupportController.class }, 14 | properties = { "management.endpoints.web.exposure.include=beans,userbeans" } 15 | ) 16 | class UserBeansServiceTests { 17 | 18 | @Autowired 19 | private UserBeansService userBeansService; 20 | 21 | // @formatter:off 22 | @Test 23 | void shouldReturnsAllBeansInformation() { 24 | //Given 25 | //When 26 | var beanList = userBeansService.getBeansDocuments(); 27 | 28 | //Then 29 | assertThat(beanList) 30 | .isSortedAccordingTo(Comparator.comparing(UserBeansService.BeanDocument::beanName)) 31 | .hasSizeGreaterThan(0); 32 | } 33 | 34 | // @formatter:on 35 | 36 | @Test 37 | void shouldIncludeSpecificSupportTest() { 38 | //Given 39 | //When 40 | var beanListFiltered = userBeansService 41 | .getBeansDocuments() 42 | .stream() 43 | .filter(beanDocument -> beanDocument.beanPackage().contains("io.github.jabrena.support")) 44 | .toList(); 45 | 46 | //Then 47 | assertThat(beanListFiltered).hasSize(2); 48 | } 49 | 50 | // @formatter:off 51 | 52 | @Test 53 | void shouldNoReturnAnyUnnamedBean() { 54 | //Given 55 | var expectedUnnamedBeanCounter = 0; 56 | 57 | //When 58 | var beanList = userBeansService.getBeansDocuments().stream() 59 | .filter(bd -> bd.beanName().equals("")) 60 | .toList(); 61 | 62 | //Then 63 | assertThat(beanList).hasSize(expectedUnnamedBeanCounter); 64 | } 65 | // @formatter:on 66 | 67 | } 68 | -------------------------------------------------------------------------------- /examples/hello-world-reactive/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.github.jabrena 8 | examples 9 | 0.3.0-SNAPSHOT 10 | 11 | 12 | io.github.jabrena 13 | hello-world-reactive 14 | 0.3.0-SNAPSHOT 15 | hello-world-reactive 16 | Demo project for Spring Boot 17 | jar 18 | 19 | 20 | 17 21 | com.example.demo.DemoApplication 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-webflux 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-devtools 37 | runtime 38 | true 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | ${spring-boot.version} 54 | 55 | false 56 | 57 | 58 | 59 | 60 | repackage 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | userbeans 71 | 72 | 73 | 74 | io.github.jabrena 75 | spring-boot-starter-user-beans 76 | 0.1.0-SNAPSHOT 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /examples/hello-world-servlet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.github.jabrena 8 | examples 9 | 0.3.0-SNAPSHOT 10 | 11 | 12 | io.github.jabrena 13 | hello-world-servlet 14 | 0.3.0-SNAPSHOT 15 | hello-world-servlet 16 | Demo project for Spring Boot 17 | jar 18 | 19 | 20 | 17 21 | com.example.demo.DemoApplication 22 | true 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-actuator 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | io.github.jabrena 45 | spring-boot-starter-user-beans 46 | 0.3.0-SNAPSHOT 47 | 48 | 49 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | ${spring-boot.version} 64 | 65 | false 66 | 67 | 68 | 69 | 70 | repackage 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | jitpack.io 81 | https://jitpack.io 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/UserBeansService.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import java.util.Arrays; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | import java.util.function.Function; 9 | import java.util.function.UnaryOperator; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.boot.actuate.beans.BeansEndpoint; 13 | import org.springframework.boot.actuate.beans.BeansEndpoint.ContextBeansDescriptor; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | public class UserBeansService { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(UserBeansService.class); 20 | 21 | private final BeansEndpoint beansEndpoint; 22 | 23 | public UserBeansService(BeansEndpoint beansEndpoint) { 24 | this.beansEndpoint = beansEndpoint; 25 | } 26 | 27 | public record BeanDocument(String beanName, String beanPackage, List dependencies) {} 28 | 29 | public List getBeansDocuments() { 30 | logger.info("Generating Beans information"); 31 | Map beansMap = beansEndpoint.beans().getContexts(); 32 | var contextBeansDescriptorList = beansMap.values().stream().toList(); 33 | return contextBeansDescriptorList 34 | .stream() 35 | .flatMap(cd -> cd.getBeans().entrySet().stream()) 36 | .map(toBeanDocument) 37 | .sorted(Comparator.comparing(BeanDocument::beanName)) 38 | .toList(); 39 | } 40 | 41 | private UnaryOperator removePackage = beanName -> { 42 | var beanNameParts = beanName.split("\\."); 43 | return (beanNameParts.length > 0) ? beanNameParts[beanNameParts.length - 1] : beanName; 44 | }; 45 | 46 | // @formatter:off 47 | private Function, BeanDocument> toBeanDocument = bean -> { 48 | String beanName = (bean.getValue().getType().getSimpleName().length() == 0) ? bean.getKey() : bean.getValue().getType().getSimpleName(); 49 | Class beanClass = bean.getValue().getType(); 50 | String packageName = beanClass.getPackageName(); 51 | List dependencies = Arrays 52 | .stream(bean.getValue().getDependencies()) 53 | .map(removePackage) 54 | .toList(); 55 | return new BeanDocument(beanName, packageName, dependencies); 56 | }; 57 | // @formatter:on 58 | } 59 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/resources/static/details.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | Beans Details 12 | 13 | 14 |

Details for

15 |

16 |

17 |

Waiting for a ChatGPT Answer

18 | Go back 19 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/UserBeansEndpoint.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import java.util.List; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | @RestControllerEndpoint(id = "userbeans") 14 | public class UserBeansEndpoint { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(UserBeansEndpoint.class); 17 | 18 | @Autowired 19 | private UserBeansGraphService userBeansGraphService; 20 | 21 | @Autowired 22 | private JavaClassExplanationService beanExplanationService; 23 | 24 | @GetMapping(path = "/", produces = MediaType.TEXT_HTML_VALUE) 25 | ResponseEntity loadGraphWebDocument() { 26 | logger.info("GET /actuator/userbeans"); 27 | return ResponseEntity.ok().body(userBeansGraphService.generateGraphWebDocument()); 28 | } 29 | 30 | // @formatter:off 31 | @GetMapping(path = "/graph", produces = MediaType.APPLICATION_JSON_VALUE) 32 | ResponseEntity getGraph( 33 | @RequestParam(name = "dependency", required = false) String dependency) { 34 | logger.info("GET /actuator/userbeans/graph"); 35 | return ResponseEntity.ok().body(userBeansGraphService.generateGraphData(dependency)); 36 | } 37 | 38 | // @formatter:on 39 | 40 | @GetMapping(path = "/graph-combo", produces = MediaType.APPLICATION_JSON_VALUE) 41 | ResponseEntity> getGraphCombo() { 42 | logger.info("GET /actuator/userbeans/graph-combo"); 43 | return ResponseEntity.ok().body(userBeansGraphService.generateGraphCombo()); 44 | } 45 | 46 | @GetMapping(path = "/details", produces = MediaType.TEXT_HTML_VALUE) 47 | ResponseEntity loadDetailsWebDocument() { 48 | logger.info("GET /actuator/userbeans/details"); 49 | return ResponseEntity.ok().body(beanExplanationService.generateDetailsWebDocument()); 50 | } 51 | 52 | // @formatter:off 53 | @GetMapping(path = "/details-explanation", produces = MediaType.APPLICATION_JSON_VALUE) 54 | ResponseEntity loadDetailsContentWebDocument( 55 | @RequestParam(name = "class") String bean, 56 | @RequestParam(name = "package") String packageName, 57 | @RequestParam(name = "dependency") String dependency) { 58 | logger.info("GET /actuator/userbeans/details-explanation"); 59 | return ResponseEntity.ok().body(beanExplanationService.generateDetailsContent(bean, packageName, dependency)); 60 | } 61 | // @formatter:on 62 | } 63 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/ClasspathDependencyReader.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.Enumeration; 10 | import java.util.List; 11 | import java.util.function.UnaryOperator; 12 | import java.util.jar.JarEntry; 13 | import java.util.jar.JarFile; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | public class ClasspathDependencyReader { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ClasspathDependencyReader.class); 20 | 21 | record ClasspathDependency(String dependency) {} 22 | 23 | public record DependencyPackage(String dependencyName, String packageName) {} 24 | 25 | List getClasspathDependencies() { 26 | String classpath = System.getProperty("java.class.path"); 27 | String[] classpathEntries = classpath.split(File.pathSeparator); 28 | 29 | return Arrays 30 | .stream(classpathEntries) 31 | .filter(classpathEntry -> classpathEntry.contains(".jar")) 32 | .map(ClasspathDependency::new) 33 | .sorted(Comparator.comparing(ClasspathDependency::dependency)) 34 | .toList(); 35 | } 36 | 37 | private UnaryOperator removePath = fullPath -> { 38 | var pathParts = fullPath.split("\\/"); 39 | return (pathParts.length > 0) ? pathParts[pathParts.length - 1] : fullPath; 40 | }; 41 | 42 | private List listPackagesInJar(String jarPath) { 43 | List packages = new ArrayList<>(); 44 | 45 | try (JarFile jarFile = new JarFile(new File(jarPath))) { 46 | Enumeration entries = jarFile.entries(); 47 | List entryList = Collections.list(entries); 48 | 49 | for (JarEntry entry : entryList) { 50 | if (entry.isDirectory()) { 51 | String packagePath = entry.getName().replace('/', '.'); 52 | if (!packagePath.isEmpty() && !packagePath.startsWith("META-INF")) { 53 | packagePath = packagePath.substring(0, packagePath.length() - 1); 54 | packages.add(packagePath); 55 | } 56 | } 57 | } 58 | } catch (IOException e) { 59 | logger.warn(e.getMessage()); 60 | } 61 | return packages; 62 | } 63 | 64 | List getDependencyPackages() { 65 | return getClasspathDependencies() 66 | .stream() 67 | .map(ClasspathDependency::dependency) 68 | .flatMap(classpathEntry -> { 69 | String jar = removePath.apply(classpathEntry); 70 | List pkgs = listPackagesInJar(classpathEntry); 71 | return pkgs.stream().map(pkg -> new DependencyPackage(jar, pkg)); 72 | }) 73 | .toList(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/userbeans/ChatGPTProviderTests.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; 4 | import static com.github.tomakehurst.wiremock.client.WireMock.post; 5 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | import com.github.tomakehurst.wiremock.WireMockServer; 9 | import io.github.jabrena.support.TestApplication; 10 | import org.junit.jupiter.api.AfterEach; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.context.TestPropertySource; 17 | 18 | @SpringBootTest(classes = { TestApplication.class }) 19 | @TestPropertySource(properties = { "userbeans.openapi.url=http://localhost:8090/openapi", "userbeans.openapi.apikey=XXXYYYZZZ" }) 20 | @EnableAutoConfiguration(exclude = { UserBeansGraphService.class, UserBeansService.class, UserBeansEndpoint.class }) 21 | class ChatGPTProviderTests { 22 | 23 | @Autowired 24 | private ChatGPTProvider chatGPTProvider; 25 | 26 | WireMockServer wireMockServer; 27 | 28 | @BeforeEach 29 | public void setup() { 30 | wireMockServer = new WireMockServer(8090); 31 | wireMockServer.start(); 32 | } 33 | 34 | @AfterEach 35 | public void teardown() { 36 | wireMockServer.stop(); 37 | } 38 | 39 | @Test 40 | void shouldWorkTheIntegrationIfConfigured() throws Exception { 41 | // @formatter:off 42 | 43 | //Given 44 | wireMockServer.stubFor(post(urlEqualTo("/openapi")) 45 | .willReturn(aResponse() 46 | .withHeader("Content-Type", "application/json") 47 | .withStatus(200) 48 | .withBodyFile("200-ok.json")) 49 | ); 50 | 51 | // @formatter:on 52 | 53 | //When 54 | var response = chatGPTProvider.getAnswer(""); 55 | 56 | //Then 57 | assertThat(response).as("Response should not be null").isNotNull(); 58 | } 59 | 60 | @Test 61 | void shouldHandleBadKeyScenario() throws Exception { 62 | // @formatter:off 63 | 64 | //Given 65 | wireMockServer.stubFor(post(urlEqualTo("/openapi")) 66 | .willReturn(aResponse() 67 | .withHeader("Content-Type", "application/json") 68 | .withStatus(401) 69 | .withBodyFile("401-incorrect-api-key.json")) 70 | ); 71 | 72 | // @formatter:on 73 | 74 | //When 75 | var response = chatGPTProvider.getAnswer(""); 76 | 77 | //Then 78 | assertThat(response).as("Response should be 'Something went wrong'").isEqualTo("Something went wrong"); 79 | } 80 | 81 | @Test 82 | void shouldHandleMaxContentScenario() throws Exception { 83 | // @formatter:off 84 | 85 | //Given 86 | wireMockServer.stubFor(post(urlEqualTo("/openapi")) 87 | .willReturn(aResponse() 88 | .withHeader("Content-Type", "application/json") 89 | .withStatus(400) 90 | .withBodyFile("400-max-content-reached.json")) 91 | ); 92 | 93 | // @formatter:on 94 | 95 | //When 96 | var response = chatGPTProvider.getAnswer(""); 97 | 98 | //Then 99 | assertThat(response).as("Response should be 'Something went wrong'").isEqualTo("Something went wrong"); 100 | } 101 | 102 | @Test 103 | void shouldWorkTheIntegration() throws Exception { 104 | // @formatter:off 105 | 106 | //Given 107 | wireMockServer.stubFor(post(urlEqualTo("/openapi")) 108 | .willReturn(aResponse() 109 | .withHeader("Content-Type", "application/json") 110 | .withStatus(200) 111 | .withBodyFile("200-ok.json")) 112 | ); 113 | 114 | // @formatter:on 115 | 116 | //When 117 | var response = chatGPTProvider.getAnswer(""); 118 | 119 | //Then 120 | assertThat(response).as("Response should not be empty").isNotEmpty(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 4.0.0 7 | 8 | 9 | io.github.jabrena 10 | spring-boot-user-beans 11 | 0.3.0-SNAPSHOT 12 | 13 | 14 | io.github.jabrena 15 | spring-boot-starter-user-beans 16 | 0.3.0-SNAPSHOT 17 | jar 18 | spring-boot-starter-user-beans 19 | 20 | 21 | 17 22 | 23 | 24 | 25 | 26 | org.springframework 27 | spring-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-actuator 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-configuration-processor 36 | true 37 | 38 | 39 | com.fasterxml.jackson.core 40 | jackson-annotations 41 | 2.15.0 42 | 43 | 44 | com.fasterxml.jackson.core 45 | jackson-databind 46 | 2.15.0 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-web 53 | test 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-test 58 | test 59 | 60 | 61 | org.jsoup 62 | jsoup 63 | 1.16.1 64 | test 65 | 66 | 67 | com.github.tomakehurst 68 | wiremock-standalone 69 | 2.27.2 70 | test 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-maven-plugin 79 | ${spring-boot.version} 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-compiler-plugin 84 | 3.11.0 85 | 86 | ${java.version} 87 | ${java.version} 88 | 89 | 90 | 91 | org.jacoco 92 | jacoco-maven-plugin 93 | 0.8.10 94 | 95 | 96 | 97 | default-prepare-agent 98 | 99 | prepare-agent 100 | 101 | 102 | 103 | 104 | 105 | report 106 | verify 107 | 108 | report 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/ChatGPTProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import jakarta.annotation.PostConstruct; 6 | import java.io.IOException; 7 | import java.net.URI; 8 | import java.net.http.HttpClient; 9 | import java.net.http.HttpRequest; 10 | import java.net.http.HttpResponse; 11 | import java.util.List; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.beans.factory.annotation.Qualifier; 16 | import org.springframework.beans.factory.annotation.Value; 17 | import org.springframework.context.annotation.PropertySource; 18 | import org.springframework.stereotype.Service; 19 | 20 | @Service 21 | @PropertySource("classpath:/io/github/jabrena/userbeans/application.properties") 22 | public class ChatGPTProvider { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(ChatGPTProvider.class); 25 | 26 | @Qualifier("ChatGPTMapper") 27 | @Autowired 28 | private ObjectMapper objectMapper; 29 | 30 | @Value("${userbeans.openapi.url}") 31 | private String url; 32 | 33 | @Value("${userbeans.openapi.model}") 34 | private String model; 35 | 36 | @Value("${userbeans.openapi.max_tokens}") 37 | private Integer maxTokens; 38 | 39 | @Value("${userbeans.openapi.temperature}") 40 | private Integer temperature; 41 | 42 | private final String nokey = "nokey"; 43 | 44 | @Value("${userbeans.openapi.apikey:nokey}") 45 | private String apiKey; 46 | 47 | @PostConstruct 48 | void after() { 49 | if (apiKey.equals(nokey)) { 50 | logger.warn("Key userbeans.openapi.apikey was not defined"); 51 | } else { 52 | logger.info("Key userbeans.openapi.apikey was defined"); 53 | } 54 | } 55 | 56 | // @formatter:off 57 | public record ChaptGPTAnswer(String id, String object, Integer created, String model, List choices, Usage usage) {} 58 | 59 | public record Usage(Integer prompt_tokens, Integer completion_tokens, Integer total_tokens) {} 60 | 61 | public record Choice(String text, Integer index, Object logprobs, String finish_reason) {} 62 | 63 | public record RequestPayload(String model, String prompt, int max_tokens, int temperature) {} 64 | 65 | // @formatter:on 66 | 67 | String getAnswer(String question) { 68 | logger.info("Sending a HTTP request to ChatGPT"); 69 | 70 | String result = ""; 71 | 72 | try { 73 | HttpRequest request = prepareRequestToChatGPT(url, question, apiKey); 74 | HttpClient client = HttpClient.newHttpClient(); 75 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 76 | 77 | logger.info("Processing response from ChatGPT"); 78 | 79 | if (response.statusCode() == 200) { 80 | String responseBody = response.body(); 81 | ChaptGPTAnswer answer = objectMapper.readValue(responseBody, ChaptGPTAnswer.class); 82 | result = answer.choices().get(0).text(); 83 | } else { 84 | logger.warn("Status code: {}, Message: {}", response.statusCode(), response.body()); 85 | result = "Something went wrong"; 86 | } 87 | } catch (InterruptedException e) { 88 | result = "Something went wrong"; 89 | logger.warn(e.getMessage(), e); 90 | Thread.currentThread().interrupt(); 91 | } catch (IOException e) { 92 | result = "Something went wrong"; 93 | logger.warn(e.getMessage(), e); 94 | } 95 | return result; 96 | } 97 | 98 | private HttpRequest prepareRequestToChatGPT(String url, String question, String key) throws JsonProcessingException { 99 | return HttpRequest 100 | .newBuilder() 101 | .uri(URI.create(url)) 102 | .headers(getHeaders(key)) 103 | .POST(HttpRequest.BodyPublishers.ofString(getBodyPayload(question))) 104 | .build(); 105 | } 106 | 107 | // @formatter:off 108 | 109 | private String[] getHeaders(String key) { 110 | return List.of( 111 | "Content-Type", "application/json", 112 | "Accept", "application/json", 113 | "Authorization", "Bearer " + key) 114 | .toArray(String[]::new); 115 | } 116 | 117 | // @formatter:on 118 | 119 | private String getBodyPayload(String question) throws JsonProcessingException { 120 | RequestPayload payload = new RequestPayload(model, question, maxTokens, temperature); 121 | return objectMapper.writeValueAsString(payload); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/userbeans/UserBeansEndpointsE2eTests.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import io.github.jabrena.support.TestApplication; 6 | import java.net.URI; 7 | import java.util.List; 8 | import org.jsoup.Jsoup; 9 | import org.jsoup.nodes.Document; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.web.client.TestRestTemplate; 14 | import org.springframework.boot.test.web.server.LocalServerPort; 15 | import org.springframework.core.ParameterizedTypeReference; 16 | import org.springframework.http.HttpMethod; 17 | import org.springframework.http.HttpStatusCode; 18 | import org.springframework.http.ResponseEntity; 19 | 20 | @SpringBootTest( 21 | classes = TestApplication.class, 22 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 23 | properties = { "management.endpoints.web.exposure.include=beans,userbeans" } 24 | ) 25 | class UserBeansEndpointsE2eTests { 26 | 27 | @Autowired 28 | private TestRestTemplate restTemplate; 29 | 30 | @LocalServerPort 31 | int randomServerPort; 32 | 33 | @Test 34 | void shouldReturnGraphWebDocument() throws Exception { 35 | //Given 36 | final String baseUrl = "http://localhost:" + randomServerPort + "/actuator/userbeans"; 37 | URI uri = new URI(baseUrl); 38 | 39 | //When 40 | ResponseEntity result = this.restTemplate.getForEntity(uri, String.class); 41 | Document doc = Jsoup.parse(result.getBody()); 42 | 43 | //Then 44 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 45 | assertThat(doc).isNotNull(); 46 | } 47 | 48 | @Test 49 | void shouldGenerateTheRightData() throws Exception { 50 | //Given 51 | final String baseUrl = "http://localhost:" + randomServerPort + "/actuator/userbeans/graph"; 52 | 53 | record BeanNode(String beanName, String beanPackage, String dependency, Integer index) {} 54 | 55 | record BeanEdge(String beanName, String beanPackage, String dependency) {} 56 | 57 | record ExpectedEdge(BeanEdge source, BeanEdge target) {} 58 | 59 | record ExpectedGraphData(List nodes, List edges) {} 60 | 61 | //When 62 | // @formatter:off 63 | ResponseEntity result = this.restTemplate.exchange( 64 | baseUrl, 65 | HttpMethod.GET, 66 | null, 67 | ExpectedGraphData.class); 68 | // @formatter:on 69 | 70 | //Then 71 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 72 | assertThat(result.getBody()).isNotNull(); 73 | } 74 | 75 | @Test 76 | void shouldGenerateTheRightComboData() throws Exception { 77 | //Given 78 | final String baseUrl = "http://localhost:" + randomServerPort + "/actuator/userbeans/graph-combo"; 79 | 80 | record ExpectedDependency(String dependency) {} 81 | 82 | //When 83 | // @formatter:off 84 | ResponseEntity> result = this.restTemplate.exchange( 85 | baseUrl, 86 | HttpMethod.GET, 87 | null, 88 | new ParameterizedTypeReference<>() {}); 89 | // @formatter:on 90 | 91 | //Then 92 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 93 | assertThat(result.getBody()).isNotNull(); 94 | } 95 | 96 | @Test 97 | void shouldReturnDetailsWebDocument() throws Exception { 98 | //Given 99 | final String baseUrl = "http://localhost:" + randomServerPort + "/actuator/userbeans/details"; 100 | URI uri = new URI(baseUrl); 101 | 102 | //When 103 | ResponseEntity result = this.restTemplate.getForEntity(uri, String.class); 104 | Document doc = Jsoup.parse(result.getBody()); 105 | 106 | //Then 107 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 108 | assertThat(doc).isNotNull(); 109 | } 110 | 111 | @Test 112 | void shouldGenerateTheRightDetailsExplanationData() throws Exception { 113 | //Given 114 | String baseUrl = "http://localhost:" + randomServerPort + "/actuator/userbeans/details-explanation"; 115 | baseUrl += "?class=x&package=y&dependency=z"; 116 | URI uri = new URI(baseUrl); 117 | 118 | record ExpectedDetailsExplanation(String response) {} 119 | 120 | //When 121 | // @formatter:off 122 | ResponseEntity result = this.restTemplate.getForEntity(uri, ExpectedDetailsExplanation.class); 123 | // @formatter:on 124 | 125 | //Then 126 | assertThat(result.getStatusCode()).isEqualTo(HttpStatusCode.valueOf(200)); 127 | assertThat(result.getBody()).isNotNull(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/java/io/github/jabrena/userbeans/UserBeansGraphService.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.Set; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | import java.util.stream.Stream; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.stereotype.Service; 15 | 16 | @Service 17 | public class UserBeansGraphService { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(UserBeansGraphService.class); 20 | 21 | public static final String UNKNOWN_DEPENDENCY = "UNKNOWN"; 22 | public static final String UNKNOWN_PACKAGE = "UNKNOWN"; 23 | private final UserBeansService userBeansService; 24 | private final ClasspathDependencyReader classpathDependencyReader; 25 | 26 | private final WebDocumentReader webDocumentReader; 27 | 28 | // @formatter:off 29 | public UserBeansGraphService(UserBeansService userBeansService) { 30 | this.userBeansService = userBeansService; 31 | this.classpathDependencyReader = new ClasspathDependencyReader(); 32 | this.webDocumentReader = new WebDocumentReader(); 33 | } 34 | 35 | // @formatter:on 36 | 37 | // @formatter:off 38 | String generateGraphWebDocument() { 39 | logger.info("Generating Web Document"); 40 | String fileName = "static/graph.html"; 41 | return webDocumentReader.readFromResources(fileName); 42 | } 43 | 44 | // @formatter:on 45 | 46 | public record DependencyDocument(String beanName, String beanPackage, List beanDependencies, String dependency) {} 47 | 48 | // @formatter:off 49 | public List getDependencyDocuments() { 50 | List beanDocuments = userBeansService.getBeansDocuments(); 51 | List jars = classpathDependencyReader.getDependencyPackages(); 52 | 53 | return beanDocuments.stream() 54 | .map(bd -> { 55 | Optional matchingPackage = jars.stream() 56 | .filter(pkg -> pkg.packageName().equals(bd.beanPackage())) 57 | .findFirst(); 58 | String dependencyName = matchingPackage.map(ClasspathDependencyReader.DependencyPackage::dependencyName) 59 | .orElse(UNKNOWN_DEPENDENCY); 60 | return new DependencyDocument( 61 | bd.beanName(), 62 | bd.beanPackage(), 63 | bd.dependencies(), 64 | dependencyName 65 | ); 66 | }) 67 | .sorted(Comparator.comparing(DependencyDocument::beanName)) 68 | .toList(); 69 | } 70 | 71 | // @formatter:on 72 | 73 | public record BeanNode(String beanName, String beanPackage, String dependency, Integer index) {} 74 | 75 | public record BeanEdge(String beanName, String beanPackage, String dependency) {} 76 | 77 | public record Edge(BeanEdge source, BeanEdge target) {} 78 | 79 | public record GraphData(List nodes, List edges) {} 80 | 81 | List getNodes() { 82 | AtomicInteger counter = new AtomicInteger(0); 83 | 84 | var dependencyDocuments = this.getDependencyDocuments(); 85 | List beanNodeList = dependencyDocuments 86 | .stream() 87 | .map(dd -> new BeanNode(dd.beanName(), dd.beanPackage(), dd.dependency(), counter.incrementAndGet())) 88 | .distinct() 89 | .toList(); 90 | List listDependencies = dependencyDocuments.stream().flatMap(dd -> dd.beanDependencies().stream()).toList(); 91 | List matchingNodes = listDependencies 92 | .stream() 93 | .map(dep -> 94 | beanNodeList 95 | .stream() 96 | .filter(node -> dep.equals(node.beanName())) 97 | .findFirst() 98 | .orElse(new BeanNode(dep, UNKNOWN_PACKAGE, UNKNOWN_DEPENDENCY, counter.incrementAndGet())) 99 | ) 100 | .toList(); 101 | 102 | Set distinctBeans = new HashSet<>(beanNodeList); 103 | distinctBeans.addAll(matchingNodes); 104 | 105 | return distinctBeans.stream().sorted(Comparator.comparing(BeanNode::beanName)).toList(); 106 | } 107 | 108 | List getEdges() { 109 | return this.getDependencyDocuments() 110 | .stream() 111 | .flatMap(dd -> { 112 | BeanEdge sourceNode = new BeanEdge(dd.beanName(), dd.beanPackage(), dd.dependency()); 113 | if (!dd.beanDependencies().isEmpty()) { 114 | // @formatter:off 115 | return dd.beanDependencies().stream() 116 | .map(dep -> new Edge(sourceNode, new BeanEdge(dep, UNKNOWN_PACKAGE, UNKNOWN_DEPENDENCY))); 117 | // @formatter:on 118 | } else { 119 | //TODO False edge; an evidence to redesign the graph to be consumed for D3.js 120 | return Stream.of(new Edge(sourceNode, null)); 121 | } 122 | }) 123 | .sorted(Comparator.comparing(edge -> edge.source().beanName())) 124 | .toList(); 125 | } 126 | 127 | // @formatter:off 128 | GraphData generateGraphData(String dependencyFilter) { 129 | logger.info("Generating Graph data"); 130 | 131 | var edges = getEdges(); 132 | 133 | //TODO Remove in the future the filter. Everything will be filtered in D3.js side. 134 | if (Objects.isNull(dependencyFilter) || dependencyFilter.equals("ALL")) { 135 | return new GraphData(new ArrayList<>(), edges); 136 | } else { 137 | var filteredEdges = edges.stream() 138 | .filter(edge -> edge.source().dependency.contains(dependencyFilter)) 139 | .toList(); 140 | return new GraphData(new ArrayList<>(), filteredEdges); 141 | } 142 | } 143 | 144 | // @formatter:on 145 | 146 | public record Dependency(String dependency) {} 147 | 148 | List generateGraphCombo() { 149 | return this.getDependencyDocuments() 150 | .stream() 151 | .map(DependencyDocument::dependency) 152 | .filter(dependency -> !dependency.equals(UNKNOWN_DEPENDENCY)) 153 | .map(Dependency::new) 154 | .distinct() 155 | .sorted(Comparator.comparing(Dependency::dependency)) 156 | .toList(); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/test/java/io/github/jabrena/userbeans/UserBeansGraphServiceTests.java: -------------------------------------------------------------------------------- 1 | package io.github.jabrena.userbeans; 2 | 3 | import static io.github.jabrena.userbeans.UserBeansGraphService.UNKNOWN_DEPENDENCY; 4 | import static io.github.jabrena.userbeans.UserBeansGraphService.UNKNOWN_PACKAGE; 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | import io.github.jabrena.support.SupportController; 8 | import io.github.jabrena.support.TestApplication; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Objects; 12 | import java.util.Set; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | import org.jsoup.Jsoup; 15 | import org.jsoup.nodes.Document; 16 | import org.junit.jupiter.api.DisplayName; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.ValueSource; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.test.context.SpringBootTest; 22 | 23 | @SpringBootTest( 24 | classes = { TestApplication.class, SupportController.class }, 25 | properties = { "management.endpoints.web.exposure.include=beans,userbeans" } 26 | ) 27 | class UserBeansGraphServiceTests { 28 | 29 | @Autowired 30 | private UserBeansGraphService userBeansGraphService; 31 | 32 | @Test 33 | void shouldReturnValidGraphWebDocument() { 34 | //Given 35 | //When 36 | var html = userBeansGraphService.generateGraphWebDocument(); 37 | Document doc = Jsoup.parse(html); 38 | 39 | //Then 40 | assertThat(doc).isNotNull(); 41 | } 42 | 43 | @Test 44 | void shouldReturnGraphData() { 45 | //Given 46 | var noFilter = "ALL"; 47 | 48 | //When 49 | var resuls = userBeansGraphService.generateGraphData(noFilter); 50 | 51 | //Then 52 | assertThat(resuls.edges()).hasSizeGreaterThan(0); 53 | } 54 | 55 | @Test 56 | void shouldReturnGraphDataForUnknownDependency() { 57 | //Given 58 | var filter = "UNKNOWN"; 59 | 60 | //When 61 | var resuls = userBeansGraphService.generateGraphData(filter); 62 | 63 | //Then 64 | assertThat(resuls.edges()).hasSizeGreaterThan(0).hasSizeLessThan(20); 65 | } 66 | 67 | @Test 68 | void shouldReturnGraphDataForMicrometerCore() { 69 | //Given 70 | var filter = "micrometer-core-1.11.0.jar"; 71 | 72 | //When 73 | var resuls = userBeansGraphService.generateGraphData(filter); 74 | 75 | //Then 76 | assertThat(resuls.edges()).hasSizeGreaterThan(0).hasSizeLessThan(30); 77 | } 78 | 79 | @Test 80 | void shouldReturnGraphDataForMicrometerObservation() { 81 | //Given 82 | var filter = "micrometer-observation-1.11.0.jar"; 83 | 84 | //When 85 | var resuls = userBeansGraphService.generateGraphData(filter); 86 | 87 | //Then 88 | assertThat(resuls.edges()).hasSize(1); 89 | } 90 | 91 | @Test 92 | @DisplayName("Presence of false Edges") 93 | void shouldExistEdgesWithNoTarget() { 94 | //Given 95 | var noFilter = "ALL"; 96 | 97 | //When 98 | var list = userBeansGraphService.generateGraphData(noFilter).edges().stream().filter(edge -> Objects.isNull(edge.target())).toList(); 99 | 100 | //Then 101 | assertThat(list).hasSizeGreaterThan(0); 102 | } 103 | 104 | @ParameterizedTest 105 | @ValueSource(strings = { "io.github.jabrena.support", "io.github.jabrena.userbeans" }) 106 | void shouldBePresentSpecificBeans(String beanPackage) { 107 | //Given 108 | //When 109 | var resuls = userBeansGraphService 110 | .generateGraphData("ALL") 111 | .edges() 112 | .stream() 113 | .filter(edge -> edge.source().beanPackage().contains(beanPackage)) 114 | .toList(); 115 | 116 | //Then 117 | assertThat(resuls).hasSizeGreaterThan(0); 118 | } 119 | 120 | @Test 121 | void shouldGenerateNodes() { 122 | //Given 123 | //When 124 | var resuls = userBeansGraphService.getNodes(); 125 | 126 | //Then 127 | assertThat(resuls).hasSizeGreaterThan(0); 128 | } 129 | 130 | @Test 131 | void getDependencyDocuments() { 132 | //Given 133 | //When 134 | var result = userBeansGraphService.getDependencyDocuments(); 135 | 136 | //Then 137 | 138 | assertThat(result).as("Result of dependency documents should not be empty").isNotEmpty(); 139 | } 140 | 141 | @Test 142 | void getDependencyDocumentsWithFilter() { 143 | //Given 144 | var dependency = "UNKNOWN"; 145 | 146 | // @formatter:off 147 | 148 | //When 149 | var result = userBeansGraphService.getDependencyDocuments().stream() 150 | .filter(dd -> dd.dependency().contains(dependency)) 151 | .toList(); 152 | 153 | //Then 154 | assertThat(result) 155 | .as("Result of dependency documents should not be empty") 156 | .hasSizeGreaterThan(0); 157 | // @formatter:on 158 | } 159 | 160 | @Test 161 | void getDependencyDocumentsWithFilter2() { 162 | //Given 163 | var dependency = "micrometer-observation-1.11.0.jar"; 164 | 165 | // @formatter:off 166 | 167 | //When 168 | var result = userBeansGraphService.getDependencyDocuments().stream() 169 | .filter(dd -> dd.dependency().contains(dependency)) 170 | .peek(System.out::println) 171 | .toList(); 172 | 173 | //Then 174 | assertThat(result) 175 | .as("Result of dependency documents should not be empty") 176 | .hasSizeGreaterThan(0); 177 | // @formatter:on 178 | } 179 | 180 | @Test 181 | void getDependencyDocumentsWithFilter3() { 182 | //Given 183 | var dependency = "jackson-databind-2.15.0.jar"; 184 | 185 | // @formatter:off 186 | 187 | //When 188 | var result = userBeansGraphService.getDependencyDocuments().stream() 189 | .filter(dd -> dd.dependency().contains(dependency)) 190 | .peek(System.out::println) 191 | .toList(); 192 | 193 | //Then 194 | assertThat(result) 195 | .as("Result of dependency documents should not be empty") 196 | .hasSizeGreaterThan(0); 197 | // @formatter:on 198 | } 199 | 200 | @Test 201 | void shouldProvideDifferentNodes() { 202 | //Given 203 | var expectedUnnamedBeanCounter = 0; 204 | 205 | //When 206 | AtomicInteger counter = new AtomicInteger(0); 207 | 208 | var dependencyDocuments = userBeansGraphService.getDependencyDocuments(); 209 | List beanNodeList = dependencyDocuments 210 | .stream() 211 | .map(dd -> new UserBeansGraphService.BeanNode(dd.beanName(), dd.beanPackage(), dd.dependency(), counter.incrementAndGet())) 212 | .distinct() 213 | .toList(); 214 | List listDependencies = dependencyDocuments.stream().flatMap(dd -> dd.beanDependencies().stream()).toList(); 215 | List matchingNodes = listDependencies 216 | .stream() 217 | .map(dep -> 218 | beanNodeList 219 | .stream() 220 | .filter(node -> dep.equals(node.beanName())) 221 | .findFirst() 222 | .orElse(new UserBeansGraphService.BeanNode(dep, UNKNOWN_PACKAGE, UNKNOWN_DEPENDENCY, counter.incrementAndGet())) 223 | ) 224 | .toList(); 225 | 226 | Set distinctBeans = new HashSet<>(beanNodeList); 227 | distinctBeans.addAll(matchingNodes); 228 | 229 | distinctBeans.stream().map(UserBeansGraphService.BeanNode::beanName).distinct().sorted().forEach(System.out::println); 230 | 231 | //Then 232 | assertThat(distinctBeans).hasSizeGreaterThan(expectedUnnamedBeanCounter); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.github.jabrena 7 | spring-boot-user-beans 8 | 0.3.0-SNAPSHOT 9 | spring-boot-user-beans 10 | pom 11 | 12 | 13 | UTF-8 14 | 17 15 | 16 | 17 | ${project.basedir}/coverage-module/target/site/jacoco-aggregate/jacoco.xml 18 | 19 | https://sonarcloud.io/ 20 | jabrena 21 | jabrena_${project.artifactId} 22 | 18ec9b84411fa97bd8ed0ee09c4c4c09a3291981 23 | 24 | 3.1.0 25 | 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-dependencies 32 | ${spring-boot.version} 33 | pom 34 | import 35 | 36 | 37 | 38 | 39 | 40 | spring-boot-starter-user-beans 41 | examples 42 | coverage-module 43 | 46 | 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | ${spring-boot.version} 54 | 55 | true 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-compiler-plugin 61 | 3.11.0 62 | 63 | ${java.version} 64 | ${java.version} 65 | 66 | 67 | 68 | maven-surefire-plugin 69 | 3.1.2 70 | 71 | 72 | 73 | org.jacoco 74 | jacoco-maven-plugin 75 | 0.8.10 76 | 77 | 78 | prepare-unit-tests 79 | 80 | prepare-agent 81 | 82 | 83 | 84 | jacoco-report 85 | test 86 | 87 | report 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-checkstyle-plugin 97 | 3.1.2 98 | 99 | 100 | com.puppycrawl.tools 101 | checkstyle 102 | 9.2.1 103 | 104 | 105 | 106 | 107 | src/checkstyle/google_checks.xml 108 | src/checkstyle/checkstyle-suppressions.xml 109 | true 110 | UTF-8 111 | true 112 | true 113 | warning 114 | true 115 | 0 116 | false 117 | 118 | 119 | 120 | validate 121 | validate 122 | 123 | check 124 | 125 | 126 | 127 | 128 | 129 | 130 | org.apache.maven.plugins 131 | maven-enforcer-plugin 132 | 3.0.0 133 | 134 | 135 | enforce 136 | 137 | 138 | 139 | 140 | 141 | 3.6.3 142 | 143 | 144 | 17 145 | 146 | 147 | 148 | org.projectlombok:lombok 149 | 150 | 151 | 152 | true 153 | 154 | 155 | enforce 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | com.hubspot.maven.plugins 165 | prettier-maven-plugin 166 | 0.17 167 | 168 | 1.6.1 169 | 150 170 | 4 171 | false 172 | true 173 | true 174 | 175 | 176 | 177 | src/main/java/**/*.java 178 | src/test/java/**/*.java 179 | 180 | 181 | 182 | 183 | validate 184 | 185 | check 186 | 187 | 188 | 189 | 190 | 191 | 192 | org.codehaus.mojo 193 | versions-maven-plugin 194 | 2.7 195 | 196 | 197 | 198 | 199 | 200 | 201 | pipelines 202 | 203 | 204 | 205 | 206 | org.sonarsource.scanner.maven 207 | sonar-maven-plugin 208 | 3.9.1.2184 209 | 210 | 211 | org.owasp 212 | dependency-check-maven 213 | 8.3.1 214 | 215 | 216 | 217 | check 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring boot User Beans 2 | 3 | [![CI Builds](https://github.com/jabrena/spring-boot-user-beans/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/jabrena/spring-boot-user-beans/actions/workflows/build.yaml) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-white.svg)](https://sonarcloud.io/summary/new_code?id=jabrena_spring-boot-user-beans) 6 | 7 | [![](https://jitpack.io/v/jabrena/spring-boot-user-beans.svg)](https://jitpack.io/#jabrena/spring-boot-user-beans) 8 | 9 | A visual way to increase the developer awareness to minimize the number of Beans in memory. 10 | 11 | The library exposes operational information about 12 | your Spring Beans running in the memory as another 13 | metric included with `Spring Boot Actuator`. 14 | 15 | ## Motivation 16 | 17 | Using this project, you will be able to see your Beans developed in your project 18 | plus other Beans provided by the dependencies that you include 19 | in the **classpath** in a Graph representation. The project uses Graph theory to show the beans as a [Directed Graph](https://en.m.wikipedia.org/wiki/Directed_graph). 20 | 21 | The user interfaces will allow you searching 22 | your Beans by name or package and review the relations between them. 23 | Also, you can filter by the main dependencies used in your Spring Boot project. 24 | 25 | ![](docs/design/user-beans16.png) 26 | 27 | If you click in a any Green node (A Spring Bean which returns a Java class), 28 | you could navigate to a Detail page, in order receive an explanation from ChatGPT. 29 | 30 | ## Getting Started 31 | 32 | Add the following dependency in your build system: 33 | 34 | **Maven:** 35 | 36 | ```xml 37 | 38 | 39 | jitpack.io 40 | https://jitpack.io 41 | 42 | 43 | ``` 44 | 45 | ```xml 46 | 47 | com.github.jabrena.spring-boot-user-beans 48 | spring-boot-starter-user-beans 49 | v0.2.0-SNAPSHOT 50 | 51 | ``` 52 | 53 | **Gradle:** 54 | 55 | ```kotlin 56 | allprojects { 57 | repositories { 58 | maven { url 'https://jitpack.io' } 59 | } 60 | } 61 | ``` 62 | 63 | ```gradle 64 | dependencies { 65 | implementation 'com.github.jabrena.spring-boot-user-beans:spring-boot-starter-user-beans:v0.2.0-SNAPSHOT' 66 | } 67 | ``` 68 | 69 | Further information: https://jitpack.io/#jabrena/spring-boot-user-beans/ 70 | 71 | 72 | **Note:** Coming soon on Maven Central with `JRELEASER` 73 | 74 | ### Configuration: 75 | 76 | This library require a bit of configuration in the section about 77 | `spring boot actuator`: 78 | 79 | ```properties 80 | management.endpoints.web.exposure.include=beans,userbeans 81 | ``` 82 | 83 | The library has an optional feature to explain your Beans using the ChatGPT capabilities. 84 | In order to use this feature, you need set a valid `OpenAI API Key` 85 | 86 | **Example:** 87 | 88 | ```bash 89 | export OPENAI_API_KEY=YOUR_API_KEY_VALUE 90 | echo $OPENAI_API_KEY 91 | ./mvnw spring-boot:run -Dspring-boot.run.arguments="--userbeans.openapi.apikey=$OPENAI_API_KEY" 92 | ``` 93 | 94 | **Note:** If you don´t have a `OpenAI API Key`, you can use this library but the features from `ChatGPT` 95 | will be disabled. 96 | 97 | **Requirements:** 98 | 99 | In order to use this Dependency, you need to have in the classpath: 100 | 101 | - spring-boot-starter-web || spring-boot-starter-webflux 102 | - spring-boot-starter-actuator 103 | 104 | **Testing level:** 105 | 106 | The project was tested with `Spring Boot 3.1.0` 107 | 108 | ## Benefits 109 | 110 | ### 1. Improve your Spring Beans composition 111 | 112 | By leveraging the Graph representation, 113 | you gain the ability to visualize all the Beans actively running 114 | within your Spring Boot application. Additionally, you have 115 | the flexibility to filter Beans based on their dependencies. 116 | Analyzing the Graph Shape can reveal valuable insights about 117 | the structure and relationships within your Spring solution, 118 | enabling you to make informed decisions and discover intriguing patterns. 119 | 120 | ![](docs/design/use-case1.png) 121 | 122 | ### 2. Be Stateful or not 123 | 124 | By reviewing your Beans in a broader perspective, 125 | you have the opportunity to evaluate whether your Objects 126 | need to maintain state or can be stateless. 127 | This examination allows you to make informed decisions about 128 | the design and architecture of your application, 129 | ensuring that the statefulness of your Beans aligns 130 | with the requirements and objectives of your system. 131 | 132 | ### 3. Learn about how the different Spring Boot Starters were designed. 133 | 134 | When you visit https://start.spring.io/, you gain access to a wide range 135 | of Spring Boot Starters that can be incorporated into your project. 136 | By exploring and utilizing this library, you have the opportunity 137 | to delve into the design principles behind each Starter and 138 | understand how they were specifically crafted to address various challenges. 139 | This exploration enables you to gain valuable insights into 140 | the decision-making process involved in designing these Starters, 141 | fostering a deeper understanding of Spring Boot and 142 | its versatile solutions. 143 | 144 | ![](docs/design/user-beans14.png) 145 | 146 | ### 4. Learn about the Beans that you are using under the hood. 147 | 148 | When examining the Graph, you may notice the presence of numerous Beans 149 | that are unfamiliar to you. These Beans form a critical part of 150 | the underlying Infrastructure required to run your 151 | Spring Boot application smoothly. By clicking on the Green nodes 152 | within the Graph, you can access concise descriptions that 153 | shed light on the purpose and functionality of each individual Bean. 154 | 155 | ### 5. Improve the way to measure the Cognitive Load in your team. 156 | 157 | By leveraging this educational tool, you can enhance 158 | your ability to measure and manage your 159 | [Cognitive Load](https://en.wikipedia.org/wiki/Cognitive_load). 160 | Cognitive Load refers to the mental effort required 161 | for information processing. 162 | 163 | ## Goals 164 | 165 | - [x] Visualize Beans running in the container 166 | - [x] List of user beans 167 | - [x] List of user dependencies (Jars) 168 | - [x] Review quality of results 169 | - [x] Filter by Java class, Package or dependency 170 | 171 | ## Convention over configuration 172 | 173 | [Convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) (also known as coding by convention) is a software design paradigm used by software frameworks that attempts to decrease the number of decisions that a developer using the framework is required to make without necessarily losing flexibility and don't repeat yourself (DRY) principles. 174 | 175 | ## How to build and test in local 176 | 177 | ```bash 178 | ./mvnw clean verify 179 | ./mvnw clean verify -Ppipelines 180 | ./mvnw clean spring-boot:run \ 181 | -pl examples/hello-world-servlet/ -am \ 182 | -Dspring-boot.run.arguments="--userbeans.openapi.apikey=$OPENAI_API_KEY" \ 183 | -Dmaven.repo.local=./local-m2 184 | 185 | ./mvnw spring-boot:run -pl examples/hello-world-reactive/ -am -Puserbeans -Dspring-boot.run.arguments="--userbeans.openapi.apikey=$OPENAI_API_KEY" 186 | open http://localhost:8080/ 187 | 188 | #UX 189 | curl -v http://localhost:8080/actuator/userbeans/graph | json_pp 190 | curl -v http://localhost:8080/actuator/userbeans/graph | json_pp > ./docs/graph.json 191 | curl -v http://localhost:8080/actuator/userbeans/graph-combo | json_pp 192 | curl -v http://localhost:8080/actuator/userbeans/graph-combo | json_pp > ./docs/graph-combo.json 193 | curl -v "http://localhost:8080/actuator/userbeans/graph?dependency=UNKNOWN" | json_pp 194 | ``` 195 | 196 | # External tests 197 | 198 | The project was tested with the classic project **Spring PetClinic**. 199 | 200 | ```bash 201 | ./mvnw spring-boot:run -pl external-tests/spring-petclinic -am 202 | ``` 203 | 204 | ![](docs/design/spring-petclinic.png) 205 | 206 | Go for [external-tests](./external-tests/README.md) for further details. 207 | 208 | ## How to show the coverage on Codespaces? 209 | 210 | ```bash 211 | # Step 1: Launch the webserver with the JACOCO Report 212 | ./mvnw clean verify 213 | sdk install java 20-tem 214 | sdk use java 20-tem 215 | jwebserver -p 9000 -d "$(pwd)/coverage-module/target/site/jacoco-aggregate/" 216 | 217 | # Step 2: Stop the webserver & use the default Java version 218 | sdk env install 219 | sdk env 220 | ``` 221 | 222 | ## Limitations 223 | 224 | **Graph Generation:** 225 | 226 | Currently the D3.js graph is generated only using information from Edges 227 | and it is possible to be improved. 228 | 229 | Example: `curl -v "http://localhost:8080/actuator/userbeans/graph?dependency=micrometer-observation-1.11.0.jar" | json_pp` 230 | 231 | ```json 232 | { 233 | "edges" : [ 234 | { 235 | "source" : { 236 | "beanName" : "SimpleObservationRegistry", 237 | "beanPackage" : "io.micrometer.observation", 238 | "dependency" : "micrometer-observation-1.11.0.jar" 239 | }, 240 | "target" : { 241 | "beanName" : "ObservationAutoConfiguration", 242 | "beanPackage" : "UNKNOWN", 243 | "dependency" : "UNKNOWN" 244 | } 245 | } 246 | ], 247 | "nodes" : [] 248 | } 249 | ``` 250 | 251 | This data structure need to be evolved a better way: 252 | 253 | ```js 254 | var nodes = [ 255 | {name: 'A'}, 256 | {name: 'B'}, 257 | {name: 'C'}, 258 | {name: 'D'}, 259 | {name: 'E'}, 260 | {name: 'F'}, 261 | {name: 'G'}, 262 | {name: 'H'}, 263 | ] 264 | 265 | var links = [ 266 | {source: 0, target: 1}, 267 | {source: 0, target: 2}, 268 | {source: 0, target: 3}, 269 | {source: 1, target: 6}, 270 | {source: 3, target: 4}, 271 | {source: 3, target: 7}, 272 | {source: 4, target: 5}, 273 | {source: 4, target: 7} 274 | ] 275 | ``` 276 | 277 | https://codepen.io/jabrena/pen/zYMWpJw 278 | 279 | Related issues: 280 | 281 | - https://github.com/jabrena/spring-boot-user-beans/issues/111 282 | - https://github.com/jabrena/spring-boot-user-beans/issues/85 283 | 284 | **Current limitation to get Class from deps:** 285 | 286 | The Bean `BeansEndpoint` provides a way to retrieve 287 | all Beans and dependencies, but some dependencies are not easy 288 | to retrieve the class. I believe that I need to learn more about 289 | `Proxy Beans`. 290 | 291 | ## Other commands 292 | 293 | ``` 294 | ./mvnw prettier:write 295 | ./mvnw versions:display-dependency-updates 296 | ./mvnw versions:display-plugin-updates 297 | ./mvnw versions:set -DnewVersion=0.3.0-SNAPSHOT 298 | ./mvnw versions:commit -DprocessAllModules 299 | ``` 300 | 301 | ## Source of inspiration 302 | 303 | - https://github.com/making/beansviz-spring-boot-actuator 304 | - https://github.com/spring-projects/spring-framework/issues/29973 305 | 306 | ## References 307 | 308 | - https://en.m.wikipedia.org/wiki/Directed_graph 309 | - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/package-summary.html 310 | - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/package-summary.html 311 | - https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/actuate/beans/BeansEndpoint.html 312 | - https://github.com/spring-projects-experimental/spring-boot-thin-launcher 313 | - https://docs.spring.io/spring-boot/docs/current/reference/html/cli.html#cli.using-the-cli 314 | - https://jakarta.ee/specifications/cdi/3.0/ 315 | - https://jakarta.ee/specifications/cdi/3.0/jakarta-cdi-spec-3.0.pdf 316 | - https://d3js.org/ 317 | - https://www.webjars.org/all 318 | - https://www.eclemma.org/jacoco/trunk/doc/maven.html 319 | - https://platform.openai.com/docs/api-reference/ 320 | - https://platform.openai.com/account/usage 321 | - https://platform.openai.com/account/api-keys 322 | - https://openai.com/pricing 323 | - https://jitpack.io/#jabrena/spring-boot-user-beans/ 324 | 325 | Made with ❤️ from Madrid 326 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | Directed Graph Visualization 12 | 13 | 61 | 62 | 63 | 64 | 77 |
78 | 79 | 449 | 450 | 451 | -------------------------------------------------------------------------------- /spring-boot-starter-user-beans/src/main/resources/static/graph.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | Directed Graph Visualization 12 | 13 | 61 | 62 | 63 | 64 | 77 |
78 | 79 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /src/checkstyle/google_checks.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 76 | 77 | 78 | 84 | 85 | 86 | 87 | 90 | 91 | 92 | 93 | 94 | 98 | 99 | 100 | 101 | 102 | 104 | 105 | 106 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 125 | 127 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 175 | 176 | 177 | 179 | 181 | 182 | 183 | 184 | 186 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 196 | 197 | 198 | 199 | 201 | 202 | 203 | 204 | 206 | 207 | 208 | 209 | 211 | 212 | 213 | 214 | 216 | 217 | 218 | 219 | 221 | 222 | 223 | 224 | 226 | 227 | 228 | 229 | 231 | 232 | 233 | 234 | 236 | 237 | 238 | 239 | 241 | 243 | 245 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 277 | 278 | 279 | 282 | 283 | 284 | 285 | 291 | 292 | 293 | 294 | 298 | 299 | 300 | 301 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 316 | 317 | 318 | 319 | 320 | 321 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 337 | 338 | 339 | 340 | 343 | 344 | 345 | 346 | 347 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 361 | 362 | 363 | 364 | 365 | --------------------------------------------------------------------------------