×
Back to book

Simple request response for SpringBot

Learn about the more advanced aspects of SpringBot.

This lesson provides a step-by-step tutorial for achieving your first steps in SpringBot, making use of the bot to speed up development. The tutorial includes adding custom code into both the client and server-side, and covers setting up security, validation and accessing entities' relationships using custom code.

Note: When you make any changes to code in SpringBot, you should input those changes into a protected region, so that they are not overwritten the next time the bot writes code. To understand more about protected regions, please read Protected Regions.


The Problem

In this lesson we will customising both the server-side and client-side of our application. Firstly, we will be adding a form to a custom tile with validation and persisting that data to the server. Secondly we will be creating a custom API endpoint that filters based upon input.

To achieve these tasks, we will be using a simple Solar System model.

Creating the app on the Platform

  1. In the Entity Diagram create a model with a single entity called Region, which contains a String attribute called Region Name.

  2. Create a second Solar System entity with two String attributes called System Name and Language Used in System.

  3. Add a one-to-many relationship between the entities, such that one Region can contain many Systems.

  4. When adding a new feature, the first step is to create a page on the UI Diagram to setup routing to the new page. Add a page called Example to the UI Diagram.

  5. Mark this page as the home page for your application and ensure you give it a title and a description.

    Settings for Example Page

  6. Add a custom tile to your page, and name it Example.

  7. Now you must give permission to your users to see your new page. Switch to the Security Diagram and give the Visitors access to read the Example (Page) and full permissions on the Region (Entity) and Solar System (Entity).
    Updated security diagram example

Now build your app!

Client-Side

In this section, we will go over adding a custom feature to a SpringBot client-side.

There are three files we need to interact with:

  1. example-tile.component.html
  2. example-tile.component.ts
  3. example-tile.module.ts

In this lesson, using our new page, we will create a basic entity creation form with validation.

Normally you would do this with the CRUD behaviour.

Editing the page

To edit the page, we must edit the custom tile that we placed on it.

After the bot has written the application, inside of the folder clientside/src/app/tiles/custom/example/ there will be a new file called example-tile.component.html. This file is our template for our newly created Example custom tile. This file will contain the following code:

<!-- % protected region % [Add any additional HTML structure here] off begin -->
<!-- % protected region % [Add any additional HTML structure here] end -->

Any code you write must be written inside of the protected region areas so the bot knows not to edit inside it. Code written outside of the protected regions will be deleted the next time the bot writes the application.

Setting up the new page

To perform a basic edit to the page, the protected regions must be activated so that they can be used. To do so, change the word off in the protected region comment to on. It is very important no other changes are made to the protected region comment.

Add a Hello World inside the protected region.

<!-- % protected region % [Add any additional HTML structure here] on begin -->
<p>Hello World</p>
<!-- % protected region % [Add any additional HTML structure here] end -->

Adding Interactive Data

We now have some text on the screen. Yipee! Our next step is adding something the users can interact with. We are going to make an interface which lets users create a new instance of the Region entity (which we created in the Entity Diagram).

To do this, we will make use of Angular Forms and NgRx to save the data.

Importing

First we need to define some imports in out tile's module class (This can be found at clientside/src/app/tiles/custom/example/example-tile.module.ts). This will allow us to use the inbuilt Angular components.

  1. Activate the protected region at the top of the file

  2. Add the following imports into it:

     import { CommonComponentModule } from 'src/app/lib/components/common.component.module';
     import { FormsModule, ReactiveFormsModule } from '@angular/forms';
  3. Activate the imports protected region

     // % protected region % [Add any additional module imports here] on begin 
     // % protected region % [Add any additional module imports here] end
  4. Now add the following to your imports:

     CommonComponentModule,
     FormsModule,
     ReactiveFormsModule

Your module file should now look like the following:

import { NgModule } from '@angular/core';
import { CommonModule } from "@angular/common";
import { ExampleTileComponent } from './example-tile.component';
// % protected region % [Add any additional imports here] on begin
import { CommonComponentModule } from 'src/app/lib/components/common.component.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
// % protected region % [Add any additional imports here] end

@NgModule({
    declarations: [
        ExampleTileComponent,
        // % protected region % [Add any additional declaration here] off begin
        // % protected region % [Add any additional declaration here] end
    ],
    imports: [
        CommonModule,
        // % protected region % [Add any additional module imports here] on begin
        CommonComponentModule,
        FormsModule,
        ReactiveFormsModule
        // % protected region % [Add any additional module imports here] end
    ],
    exports: [
        ExampleTileComponent,
    ],
    providers: [
        // % protected region % [Add any additional providers here] off begin
        // % protected region % [Add any additional providers here] end
    ],
    // % protected region % [Add any additional module configurations here] off begin
    // % protected region % [Add any additional module configurations here] end
})
export class ExampleTileModule {
}

The Component Class

Now for our component class. First of all, a property must be added to the ExampleTileComponent component class of our tile so we can track the Region that is going to be saved. This component class can be found at clientside/src/app/tiles/custom/example/example-tile.component.ts.

  1. First we need to import our RegionModel and some component configuration options into our component class. We will also import our persistence classes for saving our Region later.

    1. Activate the [Add any additional imports here] protected region.

    2. Copy the following code into it:

       // % protected region % [Add any additional imports here] on begin
       import { Action as NgRxAction, createAction, props, Store } from '@ngrx/store';
       import { ButtonStyle, IconPosition } from 'src/app/lib/components/button/button.component';
       import { PassableStateConfig } from 'src/app/lib/services/http/interfaces';
       import { RegionModel } from 'src/app/models/region/region.model';
       import { RegionModelState } from 'src/app/models/region/region.model.state';
       import { TextfieldType } from 'src/app/lib/components/textfield/textfield.component';
       import * as modelAction from 'src/app/models/region/region.model.action';
       import { FormBuilder, FormGroup } from '@angular/forms';
       // % protected region % [Add any additional imports here] end
  2. Now the class property can be added. We will name it regionModel and initialise it as an instance of the RegionModel that we imported in the previous step. We will also make our button component configuration options available in our template.

    1. Activate the [Add any additional class fields here] protected region.

    2. Copy the following code into it:

       // % protected region % [Add any additional class fields here] on begin
       region = new RegionModel();
       buttonStyle = ButtonStyle;
       textFieldType = TextfieldType;
       iconPos = IconPosition;
       regionForm: FormGroup;
       // % protected region % [Add any additional class fields here] end
  3. Next we will define our methods. The first one is a constructor to inject our store into our component. This is scaffolding to allow us to save our model later. We also build our form as part of this constructor.

     constructor(
         private readonly store: Store<{ model: RegionModelState }>,
         private formBuilder: FormBuilder) {
    
         this.regionForm = this.formBuilder.group({
             regionName: ''
         })
     }
  4. The second is a method called onSave. This method will allow us to change the value of the regionModel property we defined in the previous step.

     // % protected region % [Add any additional class methods here] on begin
     onSaveClick(): void {
         this.regionModel.regionName = this.regionForm.get('regionName').value;
    
         this.store.dispatch(new modelAction.CreateRegionModel(
             { targetModel: this.regionModel }
         ));
    
         this.regionForm.reset();
     }
     // % protected region % [Add any additional class methods here] end

Your component should now look like the following.

import { Component } from '@angular/core';

// % protected region % [Add any additional imports here] on begin
import { Action as NgRxAction, createAction, props, Store } from '@ngrx/store';
import { ButtonStyle, IconPosition } from 'src/app/lib/components/button/button.component';
import { PassableStateConfig } from 'src/app/lib/services/http/interfaces';
import { RegionModel } from 'src/app/models/region/region.model';
import { RegionModelState } from 'src/app/models/region/region.model.state';
import { TextfieldType } from 'src/app/lib/components/textfield/textfield.component';
import * as modelAction from 'src/app/models/region/region.model.action';
import { FormBuilder, FormGroup } from '@angular/forms';
// % protected region % [Add any additional imports here] end

@Component({
    selector: 'cb-example-tile',
    templateUrl: './example-tile.component.html',
    styleUrls: ['./example-tile.component.scss']
})
export class ExampleTileComponent {
    // % protected region % [Add any additional class fields here] on begin
    regionModel = new RegionModel();
    buttonStyle = ButtonStyle;
    textFieldType = TextfieldType;
    iconPos = IconPosition;
    regionForm: FormGroup;
    // % protected region % [Add any additional class fields here] end

    // % protected region % [Add any additional class methods here] on begin
    constructor(
        private readonly store: Store<{ model: RegionModelState }>,
        private formBuilder: FormBuilder) {

        this.regionForm = this.formBuilder.group({
            regionName: ''
        })
    }

    onSaveClick(): void {
        this.regionModel.regionName = this.regionForm.get('regionName').value;

        this.store.dispatch(new modelAction.CreateRegionModel(
            { targetModel: this.regionModel }
        ));

        this.regionForm.reset();
    }
    // % protected region % [Add any additional class methods here] end
}

Adding a text input

Our final step is to add a text input to our Example page that is bound to the regionModel property.

Replace the HTML we created in our initial "Hello World" example with the following:

<!-- % protected region % [Add any additional HTML structure here] on begin -->
<div>
    <form [formGroup]="regionForm">
        <cb-textfield [label]="'Region'"
            [name]="'Region Name'"
            [id]="'region'"
            [required]="true"
            [formControlName]="'regionName'">
        </cb-textfield>
        <button cb-button
            [buttonStyle]="buttonStyle.SOLID"
            [iconClasses]="'save'"
            [isDisabled]="!regionForm.valid"
            [iconPos]="iconPos.LEFT"
            (click)="onSaveClick()">
            Save Region
        </button>
    </form>
    <h2>{{regionModel.regionName}}</h2>
</div>
<!-- % protected region % [Add any additional HTML structure here] end -->

You will notice the [isDisabled]="!regionForm.valid" property on the button and the [required]="true" on the textfield. These both combine to add our required validator to our form.

Now you have finished you should be able to interact like follows.

Example Solution


Server-side

In this section we will be going over how to implement a custom feature inside of SpringBot. This walk-through will implement a server-side REST endpoint which fetches data from both an entity and an associated entity.

Editing the Controller

Adding the Basics

Adding a REST endpoint requires either the creation of a new controller or an existing controller to be updated. For this task, we will be making an endpoint to fetch Regions with custom conditions.

The custom condition that we will be using is retrieving regions that have a name that contains the string that is given as input.

Region Repository

We will be using the RegionRepository.java that can be found at serverside/src/main/java/<projectName>/repositories/RegionRepository.java.

This repository interface already contains a method called List<RegionEntity> findByName(@NotNull String name); We will be making an alternative method inside of the protected region called Add any additional class methods here.

This method will allow us to search by name contains and it can be done as follows:

// % protected region % [Add any additional class methods here] on begin
List<RegionEntity> findByNameContains(@NotNull String partialName);
// % protected region % [Add any additional class methods here] end

The name of this method is highly important in ensure the correct functionality. Documentation can be found on JPA can be found here.

We will make use of this in the next step.

Region Service

Open the RegionService.java file, locate and activate the protected region called Add any additional class methods here. This file can be found at serverside/src/main/java/<projectName>/services/RegionService.java.

Now place the following inside of this protected region:

// % protected region % [Add any additional class methods here] on begin
/**
* Return all the region entities .
*
* @return all the region entities
*/
@PreAuthorize("hasPermission('RegionEntity', 'read')")
public List<RegionEntity> getRegionsByNameContains(String partialName) {
    List<RegionEntity> entities = Lists.newArrayList(repository.findByNameContains(partialName));
    return entities;
}
// % protected region % [Add any additional class methods here] end

Region Controller

At the bottom of the RegionController.java file there should be the following lines. This file can be found at serverside/src/main/java/<projectName>/controllers/RegionController.java.

// % protected region % [Add any additional endpoints here] off begin
// % protected region % [Add any additional endpoints here] end

Activate this protected region and copy the following between the two tags.

// % protected region % [Add any additional endpoints here] on begin
/**
* Return all regions
*
* @return Regions
*/
@ApiOperation(
        value = "Return all regions",
        authorizations = {@Authorization(value = "bearerToken")}
)
@PreAuthorize("hasPermission('RegionsEntity', 'read')")
@GetMapping(value = "/get-regions/{partialName}", produces = "application/json")
public ResponseEntity<List<RegionEntity>> getAllRegions(@PathVariable(value="partialName") String partialName) {
    List<RegionEntity> region = regionService.getRegionsByNameContains(partialName);

    return new ResponseEntity<>(region, HttpStatus.OK);
}
// % protected region % [Add any additional endpoints here] end

Security

SpringBot security all stems from the Security diagram[^1] which allows us to configure the Create, Read, Update and Delete (CRUD) permissions for each of our entities, and our read permissions for our pages.

The server-side is responsible for ensuring the security of the data is maintained and no unauthorised access is granted to any requests.

The client-side is responsible for managing user expectations regarding access and providing feedback in the case of security exceptions.

Server-side

Server-side security is layered in it's approach and is implemented using Spring Security which is the industry standard security implementation for Java Spring applications.

For more details on how the server-side security operates for SpringBot please refer to Security handling with SpringBot.

Web Layer

The first layer is the web tier which manages the incoming requests. This layer works on a series of filters.

For the example above we will need to add an endpoint to one of our white lists found in the SecurityConfig.java file found at serverside/src/main/java/com/springbot/<projectName>/configs/security/SecurityConfig.java.

The white list we will need to adjust is the ANONYMOUS_GET_AUTH_WHITELIST.

Find the protected region called Add any custom HTTP GET whitelist routes here and add your new endpoint "/api/region/get-regions" so that appears as follows:

```java
/**
 * Allow anonymous access for requests using the HTTP GET verb for these endpoints.
 */
public static final String[] ANONYMOUS_GET_AUTH_WHITELIST = {

            /* Static files */
            "/webjars/**",
            "/assets/**",
            "/static/",
            "/",
            "/logout",
            "/index.html",

            /* Documentation */
            "/api/api-docs",
            "/swagger-resources/**",
            "/v2/api-docs",
            // % protected region % [Add any custom HTTP GET whitelist routes here] on begin
            "/api/region/get-regions"
            // % protected region % [Add any custom HTTP GET whitelist routes here] end
};
```

The purpose of this is to allow anonymous access to our new custom endpoint.

Method Layer

The second key layer to be considered is the method layer security. Method level security is enabled in MethodSecurityConfig.java as found in serverside/src/main/java/com/springbot/<projectName>/configs/security/MethodSecurityConfig.java.

What it allows us to do is apply the annotation @PreAuthorize("hasPermission('FishEntity', 'read')") on each method that we want to protected, this example allows read access to all users that have the read permission on the FishEntity as per the security diagram. These permissions are again defined in the SpringSecurity.java[^5] file, in this case in the method called setupRolesAndPrivileges.

Now, given our use of the annotation @PreAuthorize("hasPermission('RegionEntity', 'read')") in the example above and our adjustment to our security diagram to allow anonymous access, there is nothing we need to do here.

Client-side

The client-side security primarily deals with showing and hiding buttons and blocking routes based on the security diagram rules.

If you added a CRUD tile to your application, for example, for the RegionEntity you can see a region-tile-crud-list.component.ts file. If so, draw your attention to the bottom you will find three methods,

  1. canEdit
  2. canDelete
  3. canCreate

These methods define whether or not to allow a user access to the respective actions on the client-side. If a user was to gain access to one of these actions, they would be rejected by the sever due to the server-side security. Given that we should prevent users from doing things that would fail, the rules from the security diagram are also applied on the client-side.

You may notice that the list above does not have the read permission in it. This is a result of the read access control being controlled at the route level.

See example.page.routes.ts, now draw your attention to the expectedRoles array. This is where the allows roles are applied to the route.

Given that all these are controlled by the security diagram, there is nothing we need to do here.

Validation

Server-side

Server-side validation is applied on the entity level. Looking at our RegionEntity.java file we can see annotations on the class properties such as @NotNull(message = "Name must not be empty"). Additional validation can be added by activating the surrounding protected region for each property and applying additional annotations.

To see a full list of available inbuilt validators, please refer to Detailed information about Spring boot validation.

Client-side

As with the server-side, Angular also has a collection of inbuilt validators, For details please refer to this detailed information about Angular validators.

These validators can be applied in the model. For example, in our region.model.ts (in the method called getProps) each attribute has a property called validators; Validators can be applied here as needed.

For example:

{
     name: 'name',
     // % protected region % [Set displayName for Name here] off begin
     displayName: 'Name',
     // % protected region % [Set displayName for Name here] end
     type: ModelPropertyType.STRING,
     // % protected region % [Set display element type for Name here] off begin
     elementType: ElementType.INPUT,
     // % protected region % [Set display element type for Name here] end
     // % protected region % [Set isSensitive for Name here] off begin
     isSensitive: false,
     // % protected region % [Set isSensitive for Name here] end
     // % protected region % [Set readonly for Name here] off begin
     readOnly: false,
     // % protected region % [Set readonly for Name here] end
     validators: [
            Validators.required,
            // % protected region % [Add other validators for Name here] on begin
            Validators.max(10),
            Validators.min(2)
            // % protected region % [Add other validators for Name here] end
     ],
     // % protected region % [Add any additional model attribute properties for Name here] off begin
     // % protected region % [Add any additional model attribute properties for Name here] end
},

[^1]: The security diagram is where we can define what access our user groups have. We have briefly touched on the security diagram in this article but you can find more details in the Using the Security Diagram article.