You will create a custom Spring Boot Starter using the auto-configuration features.

Time: 30 minutes.

Spring Boot Starter

We are going create custom Spring Boot Starter. This starter will be a reusable client for the directory-web-security project. It will have the following features:

  • DirectoryWebClient bean that will have access to the persons in the directory. You will add a new person or list all the persons.

  • @EnableDirectoryWebClientUtils annotation that will bring a DirectoryWebClientUtils that will expose password encode using either BCRYPT (default) or PBKDF2 algorthims.

Project Structure

  1. Create a directory structure where we are going to place 3 apps:

    • demo-starter

    • directory-web-client-spring-boot-autoconfigure

    • directory-web-client-spring-boot-starter

      mkdir -p workspace/demo-starter workspace/directory-web-client-spring-boot-autoconfigure workspace/directory-web-client-spring-boot-starter
  2. In the workspace directory add the following pom.xml. This will hold all the module information and the general dependencies.

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <groupId>io.pivotal.workshop</groupId>
            <artifactId>directory-web-client</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <packaging>pom</packaging>
            <name>directory-web-client</name>
    
            <modules>
                    <module>directory-web-client-spring-boot-autoconfigure</module>
                    <module>directory-web-client-spring-boot-starter</module>
                    <module>demo-starter</module>
            </modules>
    
    
    
            <dependencyManagement>
                    <dependencies>
                            <dependency>
                                    <groupId>org.springframework.boot</groupId>
                                    <artifactId>spring-boot-dependencies</artifactId>
                                    <version>2.0.0.M7</version>
                                    <type>pom</type>
                                    <scope>import</scope>
                            </dependency>
                    </dependencies>
            </dependencyManagement>
    
    
        <repositories>
            <repository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>https://repo.spring.io/snapshot</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
            <repository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>https://repo.spring.io/milestone</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
    
        <pluginRepositories>
            <pluginRepository>
                <id>spring-snapshots</id>
                <name>Spring Snapshots</name>
                <url>https://repo.spring.io/snapshot</url>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </pluginRepository>
            <pluginRepository>
                <id>spring-milestones</id>
                <name>Spring Milestones</name>
                <url>https://repo.spring.io/milestone</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    
    
    </project>

    As you can see it has the main dependency right now spring-boot-dependencies:2.0.0.M6 and is a pom import. It defines our 3 modules.

Directory Web Client Spring Boot Starter

It’s a good idea to create custom starter by leaving at the end the keywords *-spring-boot-starter and at the beginning the technology that you are going to build.

  1. In the directory workspace/directory-web-client-spring-boot-starter add the following pom.xml file:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <groupId>io.pivotal.workshop</groupId>
            <artifactId>directory-web-client-spring-boot-starter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <packaging>jar</packaging>
    
            <name>directory-web-client-spring-boot-starter</name>
            <description>Demo project for Spring Boot</description>
    
            <properties>
                    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            </properties>
    
        <parent>  <!-- <1> -->
            <groupId>io.pivotal.workshop</groupId>
            <artifactId>directory-web-client</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath>..</relativePath>
        </parent>
    
            <dependencies>
                    <dependency>
                            <groupId>io.pivotal.workshop</groupId>   <!-- <2> -->
                            <artifactId>directory-web-client-spring-boot-autoconfigure</artifactId>
                            <version>0.0.1-SNAPSHOT</version>
                    </dependency>
            </dependencies>
    
    
    
    </project>
    1. See that we are using our parent pom.

    2. We are adding our auto-configuration app.

    Thats it, the spring-boot-starter is just a pom.xml file that declares the auto-configuration dependency.

Directory Web Client Spring Boot Autoconfigure

This project will have all the logic to be a reusable component in any project.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Directory Web Client Spring Boot Autoconfigure Project metadata with (See Figure 1.0):

    Table 1. Directory Web Client Spring Boot Autoconfigure - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    directory-web-client-spring-boot-autoconfigure

    Name:

    directory-web-client-spring-boot-autoconfigure

    Package Name:

    io.pivotal.workshop.directory

    Dependencies:

    Web, HATEOAS, Security, Lombok, Configuration Processor

    Project Type:

    Maven

    Spring Boot:

    2.0.0.M7

    Figure 1.0: Spring Initializr - http://start.spring.io

    SpringInitializr

  4. Type Web, HATEOAS, Security, Lombok and Configuration Processor in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. IMPORTANT Unzip the file in any directory you want and COPY the content in the workspace/directory-web-client-spring-boot-autoconfigure directory.

  7. Import your project in any IDE you want.

  8. Modify the pom.xml and make sure it looks like this:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
    
            <groupId>io.pivotal.workshop</groupId>
            <artifactId>directory-web-client-spring-boot-autoconfigure</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <packaging>jar</packaging>
    
            <name>directory-web-client-spring-boot-autoconfigure</name>
            <description>Demo project for Spring Boot</description>
    
            <properties>
                    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
                    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
                    <java.version>1.8</java.version>
            </properties>
    
        <parent>
            <groupId>io.pivotal.workshop</groupId>
            <artifactId>directory-web-client</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <relativePath>..</relativePath>
        </parent>
    
            <dependencies>
    
                    <dependency>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                    </dependency>
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-security</artifactId>
                    </dependency>
    
                    <dependency>
                            <groupId>org.springframework.hateoas</groupId>
                            <artifactId>spring-hateoas</artifactId>
                    </dependency>
    
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-web</artifactId>
                    </dependency>
    
    
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-test</artifactId>
                            <scope>test</scope>
                    </dependency>
                    <dependency>
                            <groupId>org.springframework.security</groupId>
                            <artifactId>spring-security-test</artifactId>
                            <scope>test</scope>
                    </dependency>
    
                    <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                            <optional>true</optional>
                    </dependency>
    
    
    
            </dependencies>
    
    
    </project>
  9. Let’s start by telling Spring Boot where to go for the auto-configuration. Create in the src/main/resources directory, a new folder named: META-INF and inside this folder a file named: spring.factories with the following content:

    src/main/resources/META-INF/spring.factories
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.pivotal.workshop.directory.configuration.DirectoryWebClientAutoConfiguration

    Here we are declaring the class that will do the auto-configuration, in this case the io.pivotal.workshop.directory.configuration.DirectoryWebClientAutoConfiguration class.

  10. Next let’s create the io.pivotal.workshop.directory.configuration.DirectoryWebClientAutoConfiguration class:

    src/main/java/io/pivotal/workshop/directory/configuration/DirectoryWebClientAutoConfiguration.java
    package io.pivotal.workshop.directory.configuration;
    
    import io.pivotal.workshop.directory.client.DirectoryWebClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.hateoas.Resource;
    import org.springframework.web.client.RestTemplate;
    
    //(1)
    @Configuration
    @ConditionalOnClass({Resource.class,RestTemplateBuilder.class})
    @EnableConfigurationProperties(DirectoryWebClientProperties.class)
    public class DirectoryWebClientAutoConfiguration {
    
        private final Logger log = LoggerFactory.getLogger(DirectoryWebClientAutoConfiguration.class);
        private DirectoryWebClientProperties webClientProperties;
        private RestTemplateBuilder restTemplateBuilder;
    
        //(2)
        public DirectoryWebClientAutoConfiguration(DirectoryWebClientProperties webClientProperties, RestTemplateBuilder restTemplateBuilder) {
            this.webClientProperties = webClientProperties;
            this.restTemplateBuilder = restTemplateBuilder;
        }
    
        //(3)
        @Bean
        public DirectoryWebClient directoryClient(){
            log.info("Creating a Directory Web Client...");
            return new DirectoryWebClient(restTemplate(),this.webClientProperties);
        }
    
    
        //(4)
        @Bean
        public RestTemplate restTemplate(){
            // Make sure the directory-web-security has the httpBasic() security enabled and NOT the formLogin()
            log.info("Setting up admin credentials for Directory Web Client ...");
            return this.restTemplateBuilder.basicAuthorization(webClientProperties.getUsername(),webClientProperties.getPassword()).build();
        }
    
    
    }
    1. This class is annotated with the @ConditionalOnClass that checks if in the classpath is the Resource and the RestTemplateBuilder classes, and if they do, then it will configure all the beans declared in this class.

    2. The constructor injects the DirectoryWebClientProperties and the RestTemplateBuilder. The DirectoryWebClientProperties will bring the username and password (the admin credentials for the directory-web-security app.), and the RestTemplateBuilder will create the RestTemplate instance, needed for do all the rest operations.

    3. The directoryClient is the one created to do all the rest operations over the directory-web-security app endpoints.

    4. This is the RestTeamplate that holds the administrator credentials so it can do all the Rest operations.

    Tip
    Is important to make sure the directory-web-security has the httpBasic() security enabled and NOT the formLogin()
  11. Create the io.pivotal.workshop.directory.configuration.DirectoryWebClientProperties class. This class will have the admin credentials and the uri of the directory-web-security app.:

    src/main/java/io/pivotal/workshop/directory/configuration/DirectoryWebClientProperties.java
    package io.pivotal.workshop.directory.configuration;
    
    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    import java.util.Arrays;
    import java.util.List;
    
    @Data
    @ConfigurationProperties(prefix = "directory.web.client")
    public class DirectoryWebClientProperties {
    
        String username = "admin";
        String password = "admin";
        String uri = "http://localhost:8585/api/persons";
    
    }

    See that we are using the Lombok library (with the @Data annotation)so it can generate the setters and getters from the fields.

  12. Next, let’s create the actual DirectoryWebClient class that will do all the Rest operations. Create the io.pivotal.workshop.directory.client.DirectoryWebClient class:

    src/main/java/io/pivotal/workshop/directory/client/DirectoryWebClient.java
    package io.pivotal.workshop.directory.client;
    
    
    import io.pivotal.workshop.directory.configuration.DirectoryWebClientProperties;
    import io.pivotal.workshop.directory.domain.Directory;
    import io.pivotal.workshop.directory.domain.Person;
    import org.springframework.core.ParameterizedTypeReference;
    import org.springframework.hateoas.MediaTypes;
    import org.springframework.hateoas.Resource;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.RequestEntity;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.Collection;
    import java.util.Collections;
    
    import static java.net.URI.create;
    
    public class DirectoryWebClient {
    
    
        private RestTemplate restTemplate;
        private DirectoryWebClientProperties props;
    
        public DirectoryWebClient(RestTemplate restTemplate, DirectoryWebClientProperties props){
            this.restTemplate = restTemplate;
            this.props = props;
        }
    
        public Person add(Person person){
            ResponseEntity<Resource<Person>> response =
                    this.restTemplate.exchange(
                            RequestEntity.post(
                                create(this.props.getUri()))
                                .body(person)
                            ,new ParameterizedTypeReference<Resource<Person>>() {});
    
            return response.getBody().getContent();
        }
    
        public Collection<Person> getAll() {
    
            ResponseEntity<Resource<Directory>> responseEntity =
                    this.restTemplate.exchange(RequestEntity.get(create(this.props.getUri()))
                            .accept(MediaTypes.HAL_JSON)
                            .build(),new ParameterizedTypeReference<Resource<Directory>>() {});
    
            if(responseEntity.getStatusCode() == HttpStatus.OK) {
                Directory company = responseEntity.getBody().getContent();
                return company.getEmbedded().getPersons();
            }else
                return Collections.emptyList();
        }
    
    }

    See that it has (for now) two methods. To add a new person object to the directory and get all the persons in the directory. As you can see, is using a Directoy and Person domain classes.

  13. Create the necessary domains, the io.pivotal.workshop.directory.domain.Directory and io.pivotal.workshop.directory.domain.Person classes, you can copy the Person class from the directory-web-security app, without the JPA annotations:

    src/main/java/io/pivotal/workshop/directory/domain/Directory.java
    package io.pivotal.workshop.directory.domain;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.Data;
    
    import java.util.List;
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Directory {
    
        @JsonProperty("_embedded")
        public Embedded embedded;
    
        @Data
        public class Embedded {
    
            public List<Person> persons;
    
        }
    }
    src/main/java/io/pivotal/workshop/directory/domain/Person.java
    package io.pivotal.workshop.directory.domain;
    
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Person {
    
    
        private SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd");
    
        private String id;
        private String email;
        private String name;
        private String password;
        private String role = "USER";
        private boolean enabled = true;
        private Date birthday;
        private Date created;
        private Date modified;
    
        public Person() {
            this.created = new Date();
            this.modified = new Date();
        }
    
        public Person(String email, String name, String password, String birthday) {
            this();
            this.email = email;
            this.name = name;
            this.password = password;
    
            try {
                this.birthday = date.parse(birthday);
            } catch (ParseException e) {
                this.birthday = null;
            }
        }
    
        public Person(String email, String name, String password, Date birthday) {
            this();
            this.email = email;
            this.name = name;
            this.password = password;
            this.birthday = birthday;
        }
    
        public Person(String email, String name, String password, String birthday, String role, boolean enabled) {
            this(email, name, password, birthday);
            this.role = role;
            this.enabled = enabled;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public Date getCreated() {
            return created;
        }
    
        public Date getModified() {
            return modified;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    
        public boolean isEnabled() {
            return enabled;
        }
    
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "id='" + id + '\'' +
                    ", email='" + email + '\'' +
                    ", name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    ", role='" + role + '\'' +
                    ", enabled=" + enabled +
                    ", birthday=" + birthday +
                    ", date=" + date +
                    ", created=" + created +
                    ", modified=" + modified +
                    '}';
        }
    }
  14. Lets add the next feature, the @EnableDirectoryWebClientUtils that will create a DirectoryWebClientUtils bean depending of what algorithm was declared. Add the custom annotation io.pivotal.workshop.directory.annotation.EnableDirectoryWebClientUtils class and its dependency, the io.pivotal.workshop.directory.annotation.Algorithm enum:

    src/main/java/io/pivotal/workshop/directory/annotation/EnableDirectoryWebClientUtils.java
    package io.pivotal.workshop.directory.annotation;
    
    
    import io.pivotal.workshop.directory.utils.DirectoryWebClientUtilsConfiguration;
    import org.springframework.context.annotation.Import;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import(DirectoryWebClientUtilsConfiguration.class)
    public @interface EnableDirectoryWebClientUtils {
        Algorithm algorithm() default Algorithm.BCRYPT;
    }
    src/main/java/io/pivotal/workshop/directory/annotation/Algorithm.java
    package io.pivotal.workshop.directory.annotation;
    
    public enum Algorithm {
        BCRYPT, PBKDF2
    }
  15. Next let’s create the configuration needed to create the DirectoryWebClientUtils bean. Create the io.pivotal.workshop.directory.utils.DirectoryWebClientUtilsConfiguration class. This class will implement the ImportSelector interface that will be picked up by Spring Boot during the auto-configuration. It will identify if the @EnableDirectoryWebClientUtils was declared in a @Configuration class and it will create the necessary beans.

    src/main/java/io/pivotal/workshop/directory/utils/DirectoryWebClientUtilsConfiguration.java
    package io.pivotal.workshop.directory.utils;
    
    
    import io.pivotal.workshop.directory.annotation.Algorithm;
    import io.pivotal.workshop.directory.annotation.EnableDirectoryWebClientUtils;
    import org.springframework.context.annotation.ImportSelector;
    import org.springframework.core.annotation.AnnotationAttributes;
    import org.springframework.core.type.AnnotationMetadata;
    
    public class DirectoryWebClientUtilsConfiguration implements ImportSelector {
    
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            AnnotationAttributes attributes =
                    AnnotationAttributes.fromMap(
                            annotationMetadata.getAnnotationAttributes(EnableDirectoryWebClientUtils.class.getName(), false));
            Algorithm algorithm = attributes.getEnum("algorithm");
            switch(algorithm){
                case PBKDF2:
                    return new String[] {"io.pivotal.workshop.directory.utils.Pbkdf2Encoder"};
                case BCRYPT:
                    default:
                    return new String[] {"io.pivotal.workshop.directory.utils.BcryptEncoder"};
            }
        }
    }

    The DirectoryWebClientUtilsConfiguration will decide which class to configure based on the Algorithm selected.

  16. Lets create the io.pivotal.workshop.directory.utils.BCryptEncoder and io.pivotal.workshop.directory.utils.Pbkdf2Encoder configuration classes. These classes will declare the DirectoryWebClientUtils bean and it will have the Algorithm selected.

    src/main/java/io/pivotal/workshop/directory/utils/BCryptEncoder.java
    package io.pivotal.workshop.directory.utils;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @Configuration
    public class BCryptEncoder {
    
        @Bean
        public DirectoryWebClientUtils utils(){
            return new DirectoryWebClientUtils(new BCryptPasswordEncoder(16));
        }
    }
    src/main/java/io/pivotal/workshop/directory/utils/Pbkdf2Encoder.java
    package io.pivotal.workshop.directory.utils;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
    
    @Configuration
    public class Pbkdf2Encoder {
    
        @Bean
        public DirectoryWebClientUtils utils(){
            return new DirectoryWebClientUtils(new Pbkdf2PasswordEncoder());
        }
    }
  17. Next, create the io.pivotal.workshop.directory.utils.DirectoryWebClientUtils class that will be the bean created based on the Algorithm selected:

    src/main/java/io/pivotal/workshop/directory/utils/DirectoryWebClientUtils.java
    package io.pivotal.workshop.directory.utils;
    
    import lombok.Data;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    
    @Data
    public class DirectoryWebClientUtils {
    
        private PasswordEncoder encoder;
    
        public DirectoryWebClientUtils(PasswordEncoder encoder){
            this.encoder = encoder;
        }
    
    }

Demo Starter

This project will test the custom spring-boot-starter.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Demo Starter Project metadata with (See Figure 1.0):

    Table 2. Demo Starter - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    demo-starter

    Name:

    demo-starter

    Package Name:

    io.pivotal.workshop

    Project Type:

    Maven

    Spring Boot:

    2.0.0.M7

    Figure 1.0: Spring Initializr - http://start.spring.io

    SpringInitializr

  4. This is just a simple Spring Boot app, without any additional Dependencies from the Spring Initializr.

  5. Click the Generate Project button.

  6. IMPORTANT Unzip the file in any directory you want and COPY the content in the workspace/demo-starter directory.

  7. Import the project into your favorite IDE.

  8. Add the directory-web-client-spring-boot-starter dependency to your pom.xml:

    <dependency>
        <groupId>io.pivotal.workshop</groupId>
        <artifactId>directory-web-client-spring-boot-starter</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
  9. In the src/main/resources/application.properties file add the following properties:

    directory.web.client.username=admin
    directory.web.client.password=admin
    directory.web.client.uri=http://localhost:8585/api/persons

    These are the defaul values, but this give us more flexibility if the directory-web-security app has different credentials or is running in a different port.

  10. Next, we are ready to start testing. Make sure you have the directory-web-security running in the port 8585.

Testing the DirectoryWebClient bean

  1. In your demo-starter project open the DemoStarterApplication class and add the following code:

    package io.pivotal.workshop;
    
    import io.pivotal.workshop.directory.client.DirectoryWebClient;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    
    @SpringBootApplication
    public class DemoStarterApplication {
    
            public static void main(String[] args) {
                    SpringApplication.run(DemoStarterApplication.class, args);
            }
    
            @Bean
            ApplicationRunner getPersons(DirectoryWebClient client){
                    return args -> {
                            client.getAll().forEach(System.out::println);
                    };
            }
    
    }

    In the above code we are using the DirectoryWebClient client and calling the getAll() method.

  2. Run the application and you should see all the persons that are in the directory-web-security app.

Tip
If you are using the command: ./mvnw spring-boot:run remember that you need to do a: ./mvnw install in the root folder (workspace), so the directory-web-client-spring-boot-starter and its dependencies get installed so they can be used.

Testing the @EnableDirectoryWebClientUtils annotation.

  1. In your demo-starter project open the DemoStarterApplication class and add the following code:

    package io.pivotal.workshop;
    
    import io.pivotal.workshop.directory.annotation.Algorithm;
    import io.pivotal.workshop.directory.annotation.EnableDirectoryWebClientUtils;
    import io.pivotal.workshop.directory.client.DirectoryWebClient;
    import io.pivotal.workshop.directory.utils.DirectoryWebClientUtils;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    
    @EnableDirectoryWebClientUtils(algorithm = Algorithm.PBKDF2)
    @SpringBootApplication
    public class DemoStarterApplication {
    
            public static void main(String[] args) {
                    SpringApplication.run(DemoStarterApplication.class, args);
            }
    
            @Bean
            ApplicationRunner getPersons(DirectoryWebClient client){
                    return args -> {
                            client.getAll().forEach(System.out::println);
                    };
            }
    
            @Bean
            ApplicationRunner encode(DirectoryWebClientUtils utils){
                    return args -> {
                        String text = "This text will be encrypted";
                            String hash = utils.getEncoder().encode(text);
                            System.out.println(">>> ENCRYPT: " + hash);
                            System.out.println(">>> Verify: " + utils.getEncoder().matches(text,hash));
                    };
            }
    
    }
  2. Run the application and you will see the encrypted text.

CONGRATS! you have created your first custom spring-boot-starter

Challenges

  1. Comment out the @EnableDirectoryWebClientUtils, run the app again and see how the it fails because is looking for the DirectoryWebClientUtils bean. That’s the power of creating a @Enable* technology.

  2. Add the code to add a new person using the DirectoryWebClient bean.

HOMEWORK

  1. Create a Web app that has the CRUD (Create, Read, Up, Delete) Forms for all these actions. It will use the directory-web-client-spring-boot-starter:

    • It will encrypt the user password (TIP: You can use the DirectoryWebClientUtils bean)

    • Add all the missing logic for Update, Delete, etc to the directory-web-client-spring-boot-autoconfigure so it can be more reusable for this new web app.