Skip to content

Commit 98df428

Browse files
committed
Finishing Task 11
1 parent 5f47b61 commit 98df428

File tree

19 files changed

+927
-499
lines changed

19 files changed

+927
-499
lines changed

.github/workflows/maven.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Maven build
1818
run: cd examples/maven-demo && ./mvnw --batch-mode --no-transfer-progress verify --file pom.xml
1919
- name: Spring Boot build
20-
run: cd examples/spring-boot-demo/implementation && ./mvnw --batch-mode --no-transfer-progress verify --file pom.xml
20+
run: cd examples/spring-boot-demo/implementation && ./mvnw --batch-mode --no-transfer-progress verify -Pjacoco --file pom.xml
2121
- name: Spring Boot startup and API test
2222
run: |
2323
cd examples/spring-boot-demo/implementation

examples/spring-boot-demo/implementation/README-DEV.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
# Run integration tests
2020
./mvnw clean verify
21+
./mvnw clean verify -Pjacoco
2122

2223
# Check for dependency updates
2324
./mvnw versions:display-property-updates

examples/spring-boot-demo/implementation/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<maven-plugin-surefire.version>3.5.3</maven-plugin-surefire.version>
2323
<maven-plugin-failsafe.version>3.5.3</maven-plugin-failsafe.version>
2424
<maven-plugin-jacoco.version>0.8.13</maven-plugin-jacoco.version>
25+
<maven-plugin-spotless.version>2.44.5</maven-plugin-spotless.version>
2526

2627
<!-- Quality thresholds -->
2728
<coverage.level>80</coverage.level>
@@ -137,6 +138,36 @@
137138
</execution>
138139
</executions>
139140
</plugin>
141+
142+
<!-- Spotless Maven Plugin for code formatting -->
143+
<plugin>
144+
<groupId>com.diffplug.spotless</groupId>
145+
<artifactId>spotless-maven-plugin</artifactId>
146+
<version>${maven-plugin-spotless.version}</version>
147+
<configuration>
148+
<encoding>UTF-8</encoding>
149+
<java>
150+
<removeUnusedImports />
151+
<importOrder>
152+
<order>,\#</order>
153+
</importOrder>
154+
<endWithNewline />
155+
<trimTrailingWhitespace />
156+
<indent>
157+
<spaces>true</spaces>
158+
<spacesPerTab>4</spacesPerTab>
159+
</indent>
160+
</java>
161+
</configuration>
162+
<executions>
163+
<execution>
164+
<goals>
165+
<goal>check</goal>
166+
</goals>
167+
<phase>process-sources</phase>
168+
</execution>
169+
</executions>
170+
</plugin>
140171
</plugins>
141172
</build>
142173

examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/DemoApplication.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
@SpringBootApplication
77
public class DemoApplication {
88

9-
public static void main(String[] args) {
10-
SpringApplication.run(DemoApplication.class, args);
11-
}
9+
public static void main(String[] args) {
10+
SpringApplication.run(DemoApplication.class, args);
11+
}
1212

13-
}
13+
}
Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,42 @@
11
package info.jab.ms.controller;
22

3-
import org.springframework.web.bind.annotation.RestController;
4-
import org.springframework.web.bind.annotation.GetMapping;
5-
import org.springframework.web.bind.annotation.RequestParam;
6-
import org.springframework.web.bind.annotation.RequestMapping;
7-
import org.springframework.beans.factory.annotation.Autowired;
8-
import org.springframework.http.ResponseEntity;
9-
import org.springframework.http.HttpStatus;
10-
import org.springframework.http.ProblemDetail;
11-
import info.jab.ms.service.FilmService;
123
import info.jab.ms.dto.FilmDTO;
134
import info.jab.ms.entity.Film;
14-
5+
import info.jab.ms.service.FilmService;
156
import io.swagger.v3.oas.annotations.Operation;
167
import io.swagger.v3.oas.annotations.Parameter;
8+
import io.swagger.v3.oas.annotations.media.Content;
9+
import io.swagger.v3.oas.annotations.media.Schema;
1710
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1811
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1912
import io.swagger.v3.oas.annotations.tags.Tag;
20-
import io.swagger.v3.oas.annotations.media.Content;
21-
import io.swagger.v3.oas.annotations.media.Schema;
22-
2313
import jakarta.servlet.http.HttpServletRequest;
2414
import java.net.URI;
2515
import java.time.Instant;
26-
import java.util.Map;
27-
import java.util.Objects;
2816
import java.util.HashMap;
2917
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Objects;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.http.HttpStatus;
22+
import org.springframework.http.ProblemDetail;
23+
import org.springframework.http.ResponseEntity;
24+
import org.springframework.web.bind.annotation.GetMapping;
25+
import org.springframework.web.bind.annotation.RequestMapping;
26+
import org.springframework.web.bind.annotation.RequestParam;
27+
import org.springframework.web.bind.annotation.RestController;
3028

3129
/**
3230
* FilmController - REST API Controller for Film Query Operations
33-
*
31+
*
3432
* This controller provides REST endpoints for querying films from the Sakila database.
3533
* It implements the Film Query API specification for retrieving films that start with
3634
* specific letters.
37-
*
35+
*
3836
* API Endpoints:
3937
* - GET /api/v1/films - Retrieve all films or filter by starting letter
4038
* - GET /api/v1/films?startsWith=A - Retrieve films starting with letter "A"
41-
*
39+
*
4240
* Task 4.1: Create FilmController class with @RestController annotation ✅
4341
* Task 4.2: Implement GET /api/v1/films endpoint with @GetMapping ✅
4442
* Task 4.3: Add startsWith parameter with @RequestParam validation ✅
@@ -67,9 +65,9 @@ public FilmController(FilmService filmService) {
6765
* Task 4.6: Implement response formatting with films array, count, and filter ✅
6866
* Task 4.7: Add OpenAPI @Operation, @Parameter, and @ApiResponse annotations ✅
6967
* Task 4.8: Implement proper HTTP status code handling ✅
70-
*
68+
*
7169
* Retrieves films from the Sakila database, optionally filtered by starting letter.
72-
*
70+
*
7371
* @param startsWith Optional parameter to filter films by starting letter (single character A-Z)
7472
* @param request HttpServletRequest for building error responses
7573
* @return JSON response containing films array, count, and filter information or error response
@@ -79,33 +77,33 @@ public FilmController(FilmService filmService) {
7977
description = """
8078
Retrieves films from the Sakila database that start with the specified letter.
8179
The query is case-insensitive and returns film ID and title for each matching film.
82-
80+
8381
**Performance**: Query execution time is guaranteed to be under 2 seconds.
84-
82+
8583
**Expected Results**:
8684
- Letter "A": 46 films
8785
""",
8886
operationId = "getFilmsByStartingLetter"
8987
)
9088
@ApiResponses(value = {
9189
@ApiResponse(
92-
responseCode = "200",
90+
responseCode = "200",
9391
description = "Successfully retrieved films",
9492
content = @Content(
9593
mediaType = "application/json",
9694
schema = @Schema(implementation = FilmDTO.class)
9795
)
9896
),
9997
@ApiResponse(
100-
responseCode = "400",
98+
responseCode = "400",
10199
description = "Invalid parameter - startsWith must be a single letter (A-Z)",
102100
content = @Content(
103101
mediaType = "application/json",
104102
schema = @Schema(implementation = org.springframework.http.ProblemDetail.class)
105103
)
106104
),
107105
@ApiResponse(
108-
responseCode = "500",
106+
responseCode = "500",
109107
description = "Internal server error",
110108
content = @Content(
111109
mediaType = "application/json",
@@ -123,69 +121,69 @@ public ResponseEntity<?> getFilms(
123121
)
124122
@RequestParam(required = false) String startsWith,
125123
HttpServletRequest request) {
126-
124+
127125
// Task 4.4: Implement parameter validation logic (single letter, not empty)
128126
if (Objects.nonNull(startsWith)) {
129127
ValidationResult validationResult = validateStartsWithParameter(startsWith);
130128
if (!validationResult.valid()) {
131-
return createErrorResponse(validationResult.errorMessage(), request);
129+
return createErrorResponse(validationResult.errorMessage(), request);
132130
}
133131
}
134-
132+
135133
// Call service layer to get films as entities
136134
List<Film> films = filmService.findFilmEntitiesByStartingLetter(startsWith);
137-
135+
138136
// Task 4.6: Implement response formatting with films array, count, and filter
139137
// Build filter object
140138
Map<String, Object> filter = new HashMap<>();
141139
if (Objects.nonNull(startsWith) && !startsWith.trim().isEmpty()) {
142140
filter.put("startsWith", startsWith);
143141
}
144-
142+
145143
// Create FilmDTO response (Task 6.6)
146144
FilmDTO response = FilmDTO.fromEntities(films, filter);
147-
145+
148146
// Task 4.8: Implement proper HTTP status code handling
149147
return ResponseEntity.ok(response);
150148
}
151-
149+
152150
/**
153151
* Task 4.4: Implement parameter validation logic (single letter, not empty)
154-
*
152+
*
155153
* Validates the startsWith parameter to ensure it meets the API requirements:
156154
* - Must be a single character
157155
* - Must be a letter (A-Z, a-z)
158156
* - Cannot be empty or whitespace
159157
* - Cannot be numeric or special characters
160-
*
158+
*
161159
* @param startsWith The parameter value to validate
162160
* @return ValidationResult containing validation status and error message if invalid
163161
*/
164162
private ValidationResult validateStartsWithParameter(String startsWith) {
165163
if (Objects.isNull(startsWith) || startsWith.trim().isEmpty()) {
166164
return new ValidationResult(false, "Parameter 'startsWith' cannot be empty");
167165
}
168-
166+
169167
String trimmed = startsWith.trim();
170-
168+
171169
// Check if it's a single character
172170
if (trimmed.length() != 1) {
173171
return new ValidationResult(false, "Parameter 'startsWith' must be a single letter (A-Z)");
174172
}
175-
173+
176174
char character = trimmed.charAt(0);
177-
175+
178176
// Check if it's a letter (A-Z, a-z)
179177
if (!Character.isLetter(character)) {
180178
return new ValidationResult(false, "Parameter 'startsWith' must be a single letter (A-Z)");
181179
}
182-
180+
183181
return new ValidationResult(true, null);
184182
}
185-
183+
186184
/**
187185
* Creates an error response matching the format from GlobalExceptionHandler
188-
*
186+
*
189187
* @param errorMessage The error message to include in the response
190188
* @param request The HTTP request for building the error response
191189
* @return ResponseEntity with ProblemDetail matching GlobalExceptionHandler format
@@ -194,14 +192,14 @@ private ResponseEntity<ProblemDetail> createErrorResponse(String errorMessage, H
194192
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
195193
HttpStatus.BAD_REQUEST, errorMessage
196194
);
197-
195+
198196
problemDetail.setType(URI.create("https://example.com/problems/invalid-parameter"));
199197
problemDetail.setTitle("Invalid Parameter");
200198
problemDetail.setInstance(URI.create(request.getRequestURI()));
201199
problemDetail.setProperty("timestamp", Instant.now());
202-
200+
203201
return ResponseEntity.badRequest().body(problemDetail);
204202
}
205-
203+
206204
private record ValidationResult(boolean valid, String errorMessage) { }
207-
}
205+
}

0 commit comments

Comments
 (0)