├── .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 | [](https://github.com/jabrena/spring-boot-user-beans/actions/workflows/build.yaml)
4 |
5 | [](https://sonarcloud.io/summary/new_code?id=jabrena_spring-boot-user-beans)
6 |
7 | [](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 | 
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 | 
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 | 
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 | 
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 |
65 |

66 |
70 |
75 |
Alpha
76 |
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 |
65 |

66 |
70 |
75 |
Alpha
76 |
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 |
--------------------------------------------------------------------------------