├── src ├── test │ ├── resources │ │ └── example │ │ │ └── cashcard │ │ │ ├── single.json │ │ │ └── list.json │ └── java │ │ └── example │ │ └── cashcard │ │ ├── CashCardJsonTest.java │ │ └── CashCardApplicationTests.java └── main │ ├── resources │ ├── schema.sql │ ├── data.sql │ └── application.yml │ └── java │ └── example │ └── cashcard │ ├── CashCardRepository.java │ ├── CashCard.java │ ├── CashCardApplication.java │ ├── SecurityConfig.java │ └── CashCardController.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .gitignore ├── scripts └── stage-codebase.sh ├── pom.xml ├── README.md ├── mvnw.cmd ├── mvnw └── LICENSE.txt /src/test/resources/example/cashcard/single.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 99, 3 | "amount": 123.45, 4 | "owner": "sarah1" 5 | } 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natarajmb/familycashcard-spring-application-advisor-demo/main/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE cash_card 2 | ( 3 | ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 4 | AMOUNT NUMBER NOT NULL DEFAULT 0, 5 | OWNER VARCHAR(256) NOT NULL 6 | ); -------------------------------------------------------------------------------- /src/test/resources/example/cashcard/list.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"id": 99, "amount": 123.45 , "owner": "sarah1"}, 3 | {"id": 100, "amount": 1.00 , "owner": "sarah1"}, 4 | {"id": 101, "amount": 150.00, "owner": "sarah1" } 5 | ] 6 | -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (99, 123.45, 'sarah1'); 2 | INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (100, 1.00, 'sarah1'); 3 | INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (101, 150.00, 'sarah1'); 4 | INSERT INTO CASH_CARD(ID, AMOUNT, OWNER) VALUES (102, 200.00, 'kumar2'); 5 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | logging.level: 2 | # org.springframework.context: WARN 3 | # org.springframework.data: WARN 4 | # org.springframework.jdbc: WARN 5 | # example: DEBUG 6 | ROOT: INFO 7 | 8 | spring: 9 | 10 | web: 11 | locale: en_US 12 | 13 | h2: 14 | console: 15 | enabled: true 16 | 17 | jpa: 18 | defer-datasource-initialization: true 19 | 20 | kafka: 21 | streams: 22 | cache-max-size-buffering: 100 -------------------------------------------------------------------------------- /src/main/java/example/cashcard/CashCardRepository.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.data.repository.PagingAndSortingRepository; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.PageRequest; 7 | 8 | public interface CashCardRepository extends CrudRepository, PagingAndSortingRepository { 9 | CashCard findByIdAndOwner(Long id, String owner); 10 | 11 | boolean existsByIdAndOwner(Long id, String owner); 12 | 13 | Page findByOwner(String owner, PageRequest amount); 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | 3 | ### Gradle ### 4 | .gradle 5 | build/ 6 | !gradle/wrapper/gradle-wrapper.jar 7 | !**/src/main/**/build/ 8 | !**/src/test/**/build/ 9 | 10 | ### Maven ### 11 | target/ 12 | !.mvn/wrapper/maven-wrapper.jar 13 | !**/src/main/**/target/ 14 | !**/src/test/**/target/ 15 | 16 | ### STS ### 17 | .apt_generated 18 | .classpath 19 | .factorypath 20 | .project 21 | .settings 22 | .springBeans 23 | .sts4-cache 24 | bin/ 25 | !**/src/main/**/bin/ 26 | !**/src/test/**/bin/ 27 | 28 | ### IntelliJ IDEA ### 29 | .idea 30 | *.iws 31 | *.iml 32 | *.ipr 33 | out/ 34 | !**/src/main/**/out/ 35 | !**/src/test/**/out/ 36 | 37 | ### NetBeans ### 38 | /nbproject/private/ 39 | /nbbuild/ 40 | /dist/ 41 | /nbdist/ 42 | /.nb-gradle/ 43 | 44 | ### VS Code ### 45 | .vscode/ 46 | 47 | ### Spring Application Advisor 48 | .advisor 49 | -------------------------------------------------------------------------------- /.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 | # https://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.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /scripts/stage-codebase.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Stage the codebase 5 | # This script is used to checkout the codebase to the commit that corresponds to the lesson label from the commit message. 6 | 7 | # Note this file is meant to generalize the staging of the codebase via a provided LESSON_LABEL environment variable. 8 | # there are no explicit parameters for this script to maximize the decoupling of the lesson content from the staging script. 9 | 10 | # Make sure we are in the root of the repo 11 | SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) 12 | cd $SCRIPT_DIR/.. 13 | 14 | get_sha_for_lesson_label() { 15 | git log --format=format:%H --grep "$LESSON_LABEL" 16 | } 17 | 18 | checkout_to_start_commit() { 19 | if [[ ! -z "$LESSON_LABEL" ]]; then 20 | lesson_sha=$(get_sha_for_lesson_label) 21 | if [[ -z "$lesson_sha" ]]; then 22 | echo "LESSON_LABEL '$LESSON_LABEL' is not mapped to commit for this workshop." 23 | else 24 | echo "going to do the checkout" 25 | git checkout $lesson_sha 26 | fi 27 | else 28 | echo "No lesson label provided." 29 | fi 30 | } 31 | 32 | checkout_to_start_commit 33 | -------------------------------------------------------------------------------- /src/main/java/example/cashcard/CashCard.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | import java.util.Objects; 6 | 7 | public class CashCard { 8 | 9 | @Id 10 | private Long id; 11 | private Double amount; 12 | private String owner; 13 | 14 | public CashCard(Long id, Double amount, String owner) { 15 | this.id = id; 16 | this.amount = amount; 17 | this.owner = owner; 18 | } 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public void setId(Long id) { 25 | this.id = id; 26 | } 27 | 28 | public Double getAmount() { 29 | return amount; 30 | } 31 | 32 | public void setAmount(Double amount) { 33 | this.amount = amount; 34 | } 35 | 36 | public String getOwner() { 37 | return owner; 38 | } 39 | 40 | public void setOwner(String owner) { 41 | this.owner = owner; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | CashCard cashCard = (CashCard) o; 49 | return Objects.equals(id, cashCard.id) && Objects.equals(amount, cashCard.amount) && Objects.equals(owner, cashCard.owner); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(id, amount, owner); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "CashCard{" + 60 | "id=" + id + 61 | ", amount=" + amount + 62 | ", owner='" + owner + '\'' + 63 | '}'; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/example/cashcard/CashCardApplication.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.ConfigurableBootstrapContext; 6 | import org.springframework.boot.DefaultBootstrapContext; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | import org.springframework.boot.context.event.ApplicationStartingEvent; 10 | import org.springframework.context.ApplicationListener; 11 | 12 | @SpringBootApplication 13 | public class CashCardApplication { 14 | 15 | public static void main(String[] args) { 16 | 17 | MyApplicationListener myApplicationListener = new MyApplicationListener(); 18 | SpringApplication springApplication = new SpringApplication(CashCardApplication.class); 19 | springApplication.addListeners(myApplicationListener); 20 | 21 | myApplicationListener.onApplicationEvent(new MyApplicationStartingEvent(new DefaultBootstrapContext(), springApplication, args)); 22 | 23 | springApplication.run(args); 24 | 25 | } 26 | 27 | } 28 | 29 | class MyApplicationListener implements ApplicationListener { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(MyApplicationListener.class); 32 | 33 | @Override 34 | public void onApplicationEvent(MyApplicationStartingEvent event) { 35 | log.info("MyApplicationStartingEvent fired!"); 36 | } 37 | 38 | } 39 | 40 | class MyApplicationStartingEvent extends ApplicationStartingEvent { 41 | 42 | private static final Logger log = LoggerFactory.getLogger(MyApplicationStartingEvent.class); 43 | 44 | public MyApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext, SpringApplication application, String[] args) { 45 | super(bootstrapContext, application, args); 46 | log.info("My parent is Ok!"); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 3.0.13 10 | 11 | 12 | 13 | example 14 | family-cash-cards 15 | 0.0.1-SNAPSHOT 16 | Family Cash Cards 17 | Manage Family Cash Cards 18 | 19 | 20 | 17 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-security 33 | 34 | 35 | 36 | 37 | org.springframework.data 38 | spring-data-jdbc 39 | 40 | 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | 1.18.34 46 | 47 | 48 | 49 | 50 | com.h2database 51 | h2 52 | runtime 53 | 54 | 55 | 56 | com.itextpdf 57 | itextpdf 58 | 5.5.13.3 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | 69 | org.assertj 70 | assertj-core 71 | 3.24.2 72 | test 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-maven-plugin 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/example/cashcard/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 7 | import org.springframework.security.core.userdetails.User; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; 12 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 13 | import org.springframework.security.web.SecurityFilterChain; 14 | 15 | import static org.springframework.security.config.Customizer.withDefaults; 16 | 17 | @Configuration 18 | @EnableWebSecurity 19 | public class SecurityConfig { 20 | 21 | @Bean 22 | SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 23 | http. 24 | authorizeHttpRequests((authz) -> 25 | authz 26 | .requestMatchers("/cashcards/**").hasRole("CARD-OWNER") 27 | .requestMatchers("/h2-console/**").permitAll() 28 | ) 29 | .csrf().disable() 30 | .httpBasic(withDefaults()); 31 | return http.build(); 32 | } 33 | 34 | @Bean 35 | public PasswordEncoder passwordEncoder() { 36 | return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8(); 37 | } 38 | 39 | @Bean 40 | public UserDetailsService testOnlyUsers(PasswordEncoder passwordEncoder) { 41 | User.UserBuilder users = User.builder(); 42 | UserDetails sarah = users 43 | .username("sarah1") 44 | .password(passwordEncoder.encode("abc123")) 45 | .roles("CARD-OWNER") // new role 46 | .build(); 47 | UserDetails hankOwnsNoCards = users 48 | .username("hank-owns-no-cards") 49 | .password(passwordEncoder.encode("qrs456")) 50 | .roles("NON-OWNER") // new role 51 | .build(); 52 | UserDetails kumar = users 53 | .username("kumar2") 54 | .password(passwordEncoder.encode("xyz789")) 55 | .roles("CARD-OWNER") 56 | .build(); 57 | return new InMemoryUserDetailsManager(sarah, hankOwnsNoCards, kumar); 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/java/example/cashcard/CashCardJsonTest.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | 4 | import org.assertj.core.util.Arrays; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.json.JsonTest; 9 | import org.springframework.boot.test.json.JacksonTester; 10 | 11 | import java.io.IOException; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @JsonTest 16 | public class CashCardJsonTest { 17 | 18 | @Autowired 19 | private JacksonTester json; 20 | 21 | @Autowired 22 | private JacksonTester jsonList; 23 | 24 | private CashCard[] cashCards; 25 | 26 | @BeforeEach 27 | void setUp() { 28 | cashCards = Arrays.array( 29 | new CashCard(99L, 123.45, "sarah1"), 30 | new CashCard(100L, 1.00, "sarah1"), 31 | new CashCard(101L, 150.00, "sarah1")); 32 | } 33 | 34 | @Test 35 | public void cashCardSerializationTest() throws IOException { 36 | CashCard cashCard = cashCards[0]; 37 | assertThat(json.write(cashCard)).isStrictlyEqualToJson("single.json"); 38 | assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.id"); 39 | assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.id") 40 | .isEqualTo(99); 41 | assertThat(json.write(cashCard)).hasJsonPathNumberValue("@.amount"); 42 | assertThat(json.write(cashCard)).extractingJsonPathNumberValue("@.amount") 43 | .isEqualTo(123.45); 44 | } 45 | 46 | @Test 47 | public void cashCardDeserializationTest() throws IOException { 48 | String expected = 49 | """ 50 | {\ 51 | "id": 99,\ 52 | "amount": 123.45,\ 53 | "owner": "sarah1"\ 54 | }\ 55 | """; 56 | assertThat(json.parse(expected)) 57 | .isEqualTo(new CashCard(99L, 123.45, "sarah1")); 58 | assertThat(json.parseObject(expected).getId()).isEqualTo(99L); 59 | assertThat(json.parseObject(expected).getAmount()).isEqualTo(123.45); 60 | } 61 | 62 | @Test 63 | void cashCardListSerializationTest() throws IOException { 64 | assertThat(jsonList.write(cashCards)).isStrictlyEqualToJson("list.json"); 65 | } 66 | 67 | @Test 68 | void cashCardListDeserializationTest() throws IOException { 69 | String expected = 70 | """ 71 | [\ 72 | {"id": 99, "amount": 123.45 , "owner": "sarah1"},\ 73 | {"id": 100, "amount": 1.00 , "owner": "sarah1"},\ 74 | {"id": 101, "amount": 150.00, "owner": "sarah1"}\ 75 | ]\ 76 | """; 77 | assertThat(jsonList.parse(expected)).isEqualTo(cashCards); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # familycashcard-spring-application-advisor-demo 2 | 3 | Sample Spring Boot project to demonstrate Spring Application Advisor continuous upgrade capabilities. 4 | 5 | If you want to play with this sample application and 6 | do a manual upgrade, follow along this [Spring Academy Course - Spring Boot 2.7 to 3.1 Upgrade](https://spring.academy/courses/spring-boot-2-7-to-3-1-upgrade). 7 | 8 | ## Spring Application Advisor 9 | 10 | See [Spring Application Advisor Documentation](https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/index-app-advisor.html) for details. 11 | 12 | ## Install Spring Application Advisor Server 13 | 14 | * See [Spring Application Advisor Documentation](https://docs.vmware.com/en/Tanzu-Spring-Runtime/Commercial/Tanzu-Spring-Runtime/app-advisor-install-app-advisor.html) for details. 15 | 16 | * Make sure you have required JSON database files, e.g. 17 | 18 | ``` 19 | /tmp/spring-support-database/init-spring-projects.json 20 | /tmp/cve-database/maven/all-cves-by-path.ndjson 21 | ``` 22 | 23 | * Get the Spring Application Advisor binaries from the private Spring repository, e.g. `application-advisor-server.jar` 24 | 25 | * Run the Spring Application Advisor (upgrade server), e.g. 26 | 27 | ``` 28 | java -jar application-advisor-server.jar 29 | ``` 30 | 31 | * The upgrade server will run by default on http://localhost:8080 32 | 33 | * Let's configure the environment variable for it, e.g. 34 | 35 | ``` 36 | export APP_ADVISOR_SERVER=http://localhost:8080 37 | ``` 38 | 39 | ## Get Started 40 | 41 | * [Fork this project first](https://github.com/nevenc/familycashcard-spring-application-advisor-demo/fork), e.g. 42 | 43 | ``` 44 | git clone git@github.com:YOUR_NAME_HERE/familycashcard-spring-application-advisor-demo.git 45 | ``` 46 | 47 | * Open the project, e.g. 48 | 49 | ``` 50 | cd familycashcard-spring-application-advisor-demo 51 | ``` 52 | 53 | 54 | ## Run the Advisor CLI 55 | 56 | * Run the Advisor to get a build configuration, e.g. 57 | 58 | ``` 59 | advisor build-config get 60 | ``` 61 | 62 | * Publish the build configuration, e.g. 63 | 64 | ``` 65 | advisor build-config publish --url=${APP_ADVISOR_SERVER} 66 | ``` 67 | 68 | * Get the Upgrade Plan, e.g. 69 | 70 | ``` 71 | advisor upgrade-plan get --url=${APP_ADVISOR_SERVER} 72 | ``` 73 | 74 | * Apply the Upgrade Plan step, e.g. 75 | 76 | ``` 77 | advisor upgrade-plan apply --url=${APP_ADVISOR_SERVER} 78 | ``` 79 | 80 | * Alternatively, do a pull request as well. Make sure you have configured Github token (classic) for automated pull requests, e.g. 81 | 82 | ``` 83 | export GIT_TOKEN_FOR_PRS= 84 | advisor upgrade-plan apply --url=${APP_ADVISOR_SERVER} --push 85 | ``` 86 | 87 | ## Rinse and repeat 88 | 89 | * Review, and test the pull requests. 90 | 91 | * Merge once you are comfortable with the changes. 92 | 93 | * Rinse and repeat until you are at the latest available release, e.g. 94 | 95 | ``` 96 | git restore . 97 | git pull 98 | advisor build-config get 99 | advisor build-config publish --url=${APP_ADVISOR_SERVER} 100 | advisor upgrade-plan get --url=${APP_ADVISOR_SERVER} 101 | advisor upgrade-plan apply --url=${APP_ADVISOR_SERVER} --push 102 | ``` 103 | -------------------------------------------------------------------------------- /src/main/java/example/cashcard/CashCardController.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.*; 9 | import org.springframework.web.util.UriComponentsBuilder; 10 | 11 | import java.net.URI; 12 | import java.security.Principal; 13 | import java.util.List; 14 | 15 | @RestController 16 | @RequestMapping("/cashcards") 17 | public class CashCardController { 18 | private final CashCardRepository cashCardRepository; 19 | 20 | public CashCardController(CashCardRepository cashCardRepository) { 21 | this.cashCardRepository = cashCardRepository; 22 | } 23 | 24 | @GetMapping("/{requestedId}") 25 | public ResponseEntity findById(@PathVariable Long requestedId, Principal principal) { 26 | CashCard cashCard = findCashCard(requestedId, principal); 27 | if (cashCard != null) { 28 | return ResponseEntity.ok(cashCard); 29 | } else { 30 | return ResponseEntity.notFound().build(); 31 | } 32 | } 33 | 34 | @PostMapping 35 | private ResponseEntity createCashCard(@RequestBody CashCard newCashCardRequest, UriComponentsBuilder ucb) { 36 | CashCard savedCashCard = cashCardRepository.save(newCashCardRequest); 37 | URI locationOfNewCashCard = ucb 38 | .path("cashcards/{id}") 39 | .buildAndExpand(savedCashCard.getId()) 40 | .toUri(); 41 | return ResponseEntity.created(locationOfNewCashCard).build(); 42 | } 43 | 44 | @GetMapping 45 | public ResponseEntity> findAll(Pageable pageable, Principal principal) { 46 | Page page = cashCardRepository.findByOwner(principal.getName(), 47 | PageRequest.of( 48 | pageable.getPageNumber(), 49 | pageable.getPageSize(), 50 | pageable.getSortOr(Sort.by(Sort.Direction.ASC, "amount")) 51 | )); 52 | return ResponseEntity.ok(page.getContent()); 53 | } 54 | 55 | @PutMapping("/{requestedId}") 56 | private ResponseEntity putCashCard(@PathVariable Long requestedId, @RequestBody CashCard cashCardUpdate, Principal principal) { 57 | CashCard cashCard = findCashCard(requestedId, principal); 58 | if (cashCard != null) { 59 | CashCard updatedCashCard = new CashCard(requestedId, cashCardUpdate.getAmount(), principal.getName()); 60 | cashCardRepository.save(updatedCashCard); 61 | return ResponseEntity.noContent().build(); 62 | } 63 | return ResponseEntity.notFound().build(); 64 | } 65 | 66 | @DeleteMapping("/{id}") 67 | private ResponseEntity deleteCashCard(@PathVariable Long id, Principal principal) { 68 | if (cashCardRepository.existsByIdAndOwner(id, principal.getName())) { 69 | cashCardRepository.deleteById(id); 70 | return ResponseEntity.noContent().build(); 71 | } 72 | return ResponseEntity.notFound().build(); 73 | } 74 | 75 | private CashCard findCashCard(Long requestedId, Principal principal) { 76 | return cashCardRepository.findByIdAndOwner(requestedId, principal.getName()); 77 | } 78 | } -------------------------------------------------------------------------------- /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 https://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 Maven Start Up Batch script 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 M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /src/test/java/example/cashcard/CashCardApplicationTests.java: -------------------------------------------------------------------------------- 1 | package example.cashcard; 2 | 3 | import com.jayway.jsonpath.DocumentContext; 4 | import com.jayway.jsonpath.JsonPath; 5 | import net.minidev.json.JSONArray; 6 | import org.junit.jupiter.api.Disabled; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.boot.test.web.client.TestRestTemplate; 11 | import org.springframework.http.HttpEntity; 12 | import org.springframework.http.HttpMethod; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.test.annotation.DirtiesContext; 16 | 17 | import java.net.URI; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 22 | class CashCardApplicationTests { 23 | @Autowired 24 | TestRestTemplate restTemplate; 25 | 26 | @Test 27 | @Disabled 28 | void shouldReturnACashCardWhenDataIsSaved() { 29 | ResponseEntity response = restTemplate 30 | .withBasicAuth("sarah1", "abc123") 31 | .getForEntity("/cashcards/99", String.class); 32 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 33 | 34 | DocumentContext documentContext = JsonPath.parse(response.getBody()); 35 | Number id = documentContext.read("$.id"); 36 | assertThat(id).isEqualTo(99); 37 | 38 | Double amount = documentContext.read("$.amount"); 39 | assertThat(amount).isEqualTo(12.45); 40 | } 41 | 42 | @Test 43 | void shouldNotReturnACashCardWithAnUnknownId() { 44 | ResponseEntity response = restTemplate 45 | .withBasicAuth("sarah1", "abc123") 46 | .getForEntity("/cashcards/1000", String.class); 47 | 48 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 49 | assertThat(response.getBody()).isBlank(); 50 | } 51 | 52 | @Test 53 | @DirtiesContext 54 | void shouldCreateANewCashCard() { 55 | CashCard newCashCard = new CashCard(null, 250.00, "sarah1"); 56 | ResponseEntity createResponse = restTemplate 57 | .withBasicAuth("sarah1", "abc123") 58 | .postForEntity("/cashcards", newCashCard, Void.class); 59 | assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED); 60 | 61 | URI locationOfNewCashCard = createResponse.getHeaders().getLocation(); 62 | ResponseEntity getResponse = restTemplate 63 | .withBasicAuth("sarah1", "abc123") 64 | .getForEntity(locationOfNewCashCard, String.class); 65 | assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); 66 | 67 | DocumentContext documentContext = JsonPath.parse(getResponse.getBody()); 68 | Number id = documentContext.read("$.id"); 69 | Double amount = documentContext.read("$.amount"); 70 | 71 | assertThat(id).isNotNull(); 72 | assertThat(amount).isEqualTo(250.00); 73 | } 74 | 75 | @Test 76 | void shouldReturnAllCashCardsWhenListIsRequested() { 77 | ResponseEntity response = restTemplate 78 | .withBasicAuth("sarah1", "abc123") 79 | .getForEntity("/cashcards", String.class); 80 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 81 | 82 | DocumentContext documentContext = JsonPath.parse(response.getBody()); 83 | int cashCardCount = documentContext.read("$.length()"); 84 | assertThat(cashCardCount).isEqualTo(3); 85 | 86 | JSONArray ids = documentContext.read("$..id"); 87 | assertThat(ids).containsExactlyInAnyOrder(99, 100, 101); 88 | 89 | JSONArray amounts = documentContext.read("$..amount"); 90 | assertThat(amounts).containsExactlyInAnyOrder(123.45, 1.00, 150.00); 91 | } 92 | 93 | @Test 94 | void shouldReturnAPageOfCashCards() { 95 | ResponseEntity response = restTemplate 96 | .withBasicAuth("sarah1", "abc123") 97 | .getForEntity("/cashcards?page=0&size=1", String.class); 98 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 99 | 100 | DocumentContext documentContext = JsonPath.parse(response.getBody()); 101 | JSONArray page = documentContext.read("$[*]"); 102 | assertThat(page.size()).isEqualTo(1); 103 | } 104 | 105 | @Test 106 | void shouldReturnASortedPageOfCashCards() { 107 | ResponseEntity response = restTemplate 108 | .withBasicAuth("sarah1", "abc123") 109 | .getForEntity("/cashcards?page=0&size=1&sort=amount,desc", String.class); 110 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 111 | 112 | DocumentContext documentContext = JsonPath.parse(response.getBody()); 113 | JSONArray read = documentContext.read("$[*]"); 114 | assertThat(read.size()).isEqualTo(1); 115 | 116 | double amount = documentContext.read("$[0].amount"); 117 | assertThat(amount).isEqualTo(150.00); 118 | } 119 | 120 | @Test 121 | void shouldReturnASortedPageOfCashCardsWithNoParametersAndUseDefaultValues() { 122 | ResponseEntity response = restTemplate 123 | .withBasicAuth("sarah1", "abc123") 124 | .getForEntity("/cashcards", String.class); 125 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 126 | 127 | DocumentContext documentContext = JsonPath.parse(response.getBody()); 128 | JSONArray page = documentContext.read("$[*]"); 129 | assertThat(page.size()).isEqualTo(3); 130 | 131 | JSONArray amounts = documentContext.read("$..amount"); 132 | assertThat(amounts).containsExactly(1.00, 123.45, 150.00); 133 | } 134 | 135 | @Test 136 | void shouldNotReturnACashCardWhenUsingBadCredentials() { 137 | ResponseEntity response = restTemplate 138 | .withBasicAuth("BAD-USER", "abc123") 139 | .getForEntity("/cashcards/99", String.class); 140 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); 141 | 142 | response = restTemplate 143 | .withBasicAuth("sarah1", "BAD-PASSWORD") 144 | .getForEntity("/cashcards/99", String.class); 145 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); 146 | } 147 | 148 | @Test 149 | void shouldRejectUsersWhoAreNotCardOwners() { 150 | ResponseEntity response = restTemplate 151 | .withBasicAuth("hank-owns-no-cards", "qrs456") 152 | .getForEntity("/cashcards/99", String.class); 153 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); 154 | } 155 | 156 | @Test 157 | void shouldNotAllowAccessToCashCardsTheyDoNotOwn() { 158 | ResponseEntity response = restTemplate 159 | .withBasicAuth("sarah1", "abc123") 160 | .getForEntity("/cashcards/102", String.class); // kumar2's data 161 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 162 | } 163 | 164 | @Test 165 | @DirtiesContext 166 | void shouldUpdateAnExistingCashCard() { 167 | CashCard cashCardUpdate = new CashCard(null, 19.99, null); 168 | HttpEntity request = new HttpEntity<>(cashCardUpdate); 169 | ResponseEntity response = restTemplate 170 | .withBasicAuth("sarah1", "abc123") 171 | .exchange("/cashcards/99", HttpMethod.PUT, request, Void.class); 172 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); 173 | 174 | ResponseEntity getResponse = restTemplate 175 | .withBasicAuth("sarah1", "abc123") 176 | .getForEntity("/cashcards/99", String.class); 177 | assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); 178 | DocumentContext documentContext = JsonPath.parse(getResponse.getBody()); 179 | Number id = documentContext.read("$.id"); 180 | Double amount = documentContext.read("$.amount"); 181 | assertThat(id).isEqualTo(99); 182 | assertThat(amount).isEqualTo(19.99); 183 | } 184 | 185 | @Test 186 | void shouldNotUpdateACashCardThatDoesNotExist() { 187 | CashCard unknownCard = new CashCard(null, 19.99, null); 188 | HttpEntity request = new HttpEntity<>(unknownCard); 189 | ResponseEntity response = restTemplate 190 | .withBasicAuth("sarah1", "abc123") 191 | .exchange("/cashcards/99999", HttpMethod.PUT, request, Void.class); 192 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 193 | } 194 | 195 | @Test 196 | @DirtiesContext 197 | void shouldDeleteAnExistingCashCard() { 198 | ResponseEntity deleteResponse = restTemplate 199 | .withBasicAuth("sarah1", "abc123") 200 | .exchange("/cashcards/99", HttpMethod.DELETE, null, Void.class); 201 | assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT); 202 | 203 | ResponseEntity getResponse = restTemplate 204 | .withBasicAuth("sarah1", "abc123") 205 | .getForEntity("/cashcards/99", String.class); 206 | assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 207 | } 208 | 209 | @Test 210 | void shouldNotDeleteACashCardThatDoesNotExist() { 211 | ResponseEntity deleteResponse = restTemplate 212 | .withBasicAuth("sarah1", "abc123") 213 | .exchange("/cashcards/99999", HttpMethod.DELETE, null, Void.class); 214 | assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 215 | } 216 | 217 | @Test 218 | void shouldNotAllowDeletionOfCashCardsTheyDoNotOwn() { 219 | ResponseEntity deleteResponse = restTemplate 220 | .withBasicAuth("sarah1", "abc123") 221 | .exchange("/cashcards/102", HttpMethod.DELETE, null, Void.class); 222 | assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); 223 | 224 | ResponseEntity getResponse = restTemplate 225 | .withBasicAuth("kumar2", "xyz789") 226 | .getForEntity("/cashcards/102", String.class); 227 | assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK); 228 | } 229 | } -------------------------------------------------------------------------------- /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 | # https://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 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "{}" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright {yyyy} {name of copyright owner} 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------