Skip to content

Commit 5677f80

Browse files
committed
Small changes to refactor the solution
1 parent a16764d commit 5677f80

File tree

5 files changed

+61
-117
lines changed

5 files changed

+61
-117
lines changed

examples/spring-boot-demo/implementation/src/main/java/com/example/demo/controller/FilmController.java

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.beans.factory.annotation.Autowired;
88
import org.springframework.http.ResponseEntity;
99
import org.springframework.http.HttpStatus;
10+
import org.springframework.http.ProblemDetail;
1011
import com.example.demo.service.FilmService;
1112
import com.example.demo.dto.FilmResponse;
1213

@@ -18,7 +19,11 @@
1819
import io.swagger.v3.oas.annotations.media.Content;
1920
import io.swagger.v3.oas.annotations.media.Schema;
2021

22+
import jakarta.servlet.http.HttpServletRequest;
23+
import java.net.URI;
24+
import java.time.Instant;
2125
import java.util.Map;
26+
import java.util.Objects;
2227
import java.util.HashMap;
2328
import java.util.List;
2429

@@ -65,8 +70,8 @@ public FilmController(FilmService filmService) {
6570
* Retrieves films from the Sakila database, optionally filtered by starting letter.
6671
*
6772
* @param startsWith Optional parameter to filter films by starting letter (single character A-Z)
68-
* @return JSON response containing films array, count, and filter information
69-
* @throws IllegalArgumentException if startsWith parameter is invalid (not a single letter)
73+
* @param request HttpServletRequest for building error responses
74+
* @return JSON response containing films array, count, and filter information or error response
7075
*/
7176
@Operation(
7277
summary = "Query films by starting letter",
@@ -78,8 +83,6 @@ public FilmController(FilmService filmService) {
7883
7984
**Expected Results**:
8085
- Letter "A": 46 films
81-
- Letter "B": 54 films
82-
- Letter "C": 58 films
8386
""",
8487
operationId = "getFilmsByStartingLetter"
8588
)
@@ -110,18 +113,22 @@ public FilmController(FilmService filmService) {
110113
)
111114
})
112115
@GetMapping("/films")
113-
public ResponseEntity<FilmResponse> getFilms(
116+
public ResponseEntity<?> getFilms(
114117
@Parameter(
115118
name = "startsWith",
116119
description = "Filter films by starting letter (case-insensitive, single character A-Z)",
117120
example = "A",
118121
schema = @Schema(type = "string", pattern = "^[A-Za-z]$")
119122
)
120-
@RequestParam(required = false) String startsWith) {
123+
@RequestParam(required = false) String startsWith,
124+
HttpServletRequest request) {
121125

122126
// Task 4.4: Implement parameter validation logic (single letter, not empty)
123-
if (startsWith != null) {
124-
validateStartsWithParameter(startsWith);
127+
if (Objects.nonNull(startsWith)) {
128+
ValidationResult validationResult = validateStartsWithParameter(startsWith);
129+
if (!validationResult.valid()) {
130+
return createErrorResponse(validationResult.errorMessage(), request);
131+
}
125132
}
126133

127134
// Call service layer to get films
@@ -151,25 +158,55 @@ public ResponseEntity<FilmResponse> getFilms(
151158
* - Cannot be numeric or special characters
152159
*
153160
* @param startsWith The parameter value to validate
154-
* @throws IllegalArgumentException if the parameter is invalid
161+
* @return ValidationResult containing validation status and error message if invalid
155162
*/
156-
private void validateStartsWithParameter(String startsWith) {
157-
if (startsWith == null || startsWith.trim().isEmpty()) {
158-
throw new IllegalArgumentException("Parameter 'startsWith' cannot be empty");
163+
private ValidationResult validateStartsWithParameter(String startsWith) {
164+
if (Objects.isNull(startsWith) || startsWith.trim().isEmpty()) {
165+
return new ValidationResult(false, "Parameter 'startsWith' cannot be empty");
159166
}
160167

161168
String trimmed = startsWith.trim();
162169

163170
// Check if it's a single character
164171
if (trimmed.length() != 1) {
165-
throw new IllegalArgumentException("Parameter 'startsWith' must be a single letter (A-Z)");
172+
return new ValidationResult(false, "Parameter 'startsWith' must be a single letter (A-Z)");
166173
}
167174

168175
char character = trimmed.charAt(0);
169176

170177
// Check if it's a letter (A-Z, a-z)
171178
if (!Character.isLetter(character)) {
172-
throw new IllegalArgumentException("Parameter 'startsWith' must be a single letter (A-Z)");
179+
return new ValidationResult(false, "Parameter 'startsWith' must be a single letter (A-Z)");
173180
}
181+
182+
return new ValidationResult(true, null);
183+
}
184+
185+
/**
186+
* Creates an error response matching the format from GlobalExceptionHandler
187+
*
188+
* @param errorMessage The error message to include in the response
189+
* @param request The HTTP request for building the error response
190+
* @return ResponseEntity with ProblemDetail matching GlobalExceptionHandler format
191+
*/
192+
private ResponseEntity<ProblemDetail> createErrorResponse(String errorMessage, HttpServletRequest request) {
193+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
194+
HttpStatus.BAD_REQUEST, errorMessage
195+
);
196+
197+
problemDetail.setType(URI.create("https://example.com/problems/invalid-parameter"));
198+
problemDetail.setTitle("Invalid Parameter");
199+
problemDetail.setInstance(URI.create(request.getRequestURI()));
200+
problemDetail.setProperty("timestamp", Instant.now());
201+
202+
return ResponseEntity.badRequest().body(problemDetail);
174203
}
204+
205+
/**
206+
* Record to hold validation result with validation status and error message
207+
*
208+
* @param valid true if validation passed, false otherwise
209+
* @param errorMessage error message if validation failed, null if valid
210+
*/
211+
private record ValidationResult(boolean valid, String errorMessage) { }
175212
}

examples/spring-boot-demo/implementation/src/main/java/com/example/demo/controller/GlobalExceptionHandler.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,31 +40,7 @@
4040
public class GlobalExceptionHandler {
4141

4242
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
43-
44-
/**
45-
* Task 10.2: Implement handleIllegalArgumentException method
46-
*
47-
* Handles IllegalArgumentException for parameter validation errors.
48-
* Returns HTTP 400 Bad Request with RFC 7807 Problem Details format.
49-
*/
50-
@ExceptionHandler(IllegalArgumentException.class)
51-
public ResponseEntity<ProblemDetail> handleIllegalArgumentException(
52-
IllegalArgumentException ex, HttpServletRequest request) {
5343

54-
logger.warn("Invalid parameter request: {}", ex.getMessage());
55-
56-
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
57-
HttpStatus.BAD_REQUEST, ex.getMessage()
58-
);
59-
60-
problemDetail.setType(URI.create("https://example.com/problems/invalid-parameter"));
61-
problemDetail.setTitle("Invalid Parameter");
62-
problemDetail.setInstance(URI.create(request.getRequestURI()));
63-
problemDetail.setProperty("timestamp", Instant.now());
64-
65-
return ResponseEntity.badRequest().body(problemDetail);
66-
}
67-
6844
/**
6945
* Handle RuntimeException for unexpected errors.
7046
* Returns HTTP 500 Internal Server Error with RFC 7807 Problem Details format.

examples/spring-boot-demo/implementation/src/main/java/com/example/demo/dto/FilmResponse.java

Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -26,77 +26,8 @@
2626
* }
2727
* }
2828
*/
29-
public class FilmResponse {
30-
31-
@JsonProperty("films")
32-
private List<Map<String, Object>> films;
33-
34-
@JsonProperty("count")
35-
private int count;
36-
37-
@JsonProperty("filter")
38-
private Map<String, Object> filter;
39-
40-
// Default constructor
41-
public FilmResponse() {}
42-
43-
// Constructor with all fields
44-
public FilmResponse(List<Map<String, Object>> films, int count, Map<String, Object> filter) {
45-
this.films = films;
46-
this.count = count;
47-
this.filter = filter;
48-
}
49-
50-
// Getters and setters
51-
public List<Map<String, Object>> getFilms() {
52-
return films;
53-
}
54-
55-
public void setFilms(List<Map<String, Object>> films) {
56-
this.films = films;
57-
}
58-
59-
public int getCount() {
60-
return count;
61-
}
62-
63-
public void setCount(int count) {
64-
this.count = count;
65-
}
66-
67-
public Map<String, Object> getFilter() {
68-
return filter;
69-
}
70-
71-
public void setFilter(Map<String, Object> filter) {
72-
this.filter = filter;
73-
}
74-
75-
@Override
76-
public boolean equals(Object obj) {
77-
if (this == obj) return true;
78-
if (obj == null || getClass() != obj.getClass()) return false;
79-
80-
FilmResponse that = (FilmResponse) obj;
81-
return count == that.count &&
82-
films != null ? films.equals(that.films) : that.films == null &&
83-
filter != null ? filter.equals(that.filter) : that.filter == null;
84-
}
85-
86-
@Override
87-
public int hashCode() {
88-
int result = films != null ? films.hashCode() : 0;
89-
result = 31 * result + count;
90-
result = 31 * result + (filter != null ? filter.hashCode() : 0);
91-
return result;
92-
}
93-
94-
@Override
95-
public String toString() {
96-
return "FilmResponse{" +
97-
"films=" + films +
98-
", count=" + count +
99-
", filter=" + filter +
100-
'}';
101-
}
102-
}
29+
public record FilmResponse(
30+
@JsonProperty("films") List<Map<String, Object>> films,
31+
@JsonProperty("count") int count,
32+
@JsonProperty("filter") Map<String, Object> filter
33+
) {}

examples/spring-boot-demo/implementation/src/main/java/com/example/demo/repository/FilmRepository.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.example.demo.entity.Film;
44
import org.springframework.data.jdbc.repository.query.Query;
5-
import org.springframework.data.repository.CrudRepository;
5+
import org.springframework.data.repository.ListCrudRepository;
66
import org.springframework.data.repository.query.Param;
77
import org.springframework.stereotype.Repository;
88

@@ -18,7 +18,7 @@
1818
* Task 8.4: Implement findByTitleStartingWith(String prefix) method ✅
1919
*/
2020
@Repository
21-
public interface FilmRepository extends CrudRepository<Film, Integer> {
21+
public interface FilmRepository extends ListCrudRepository<Film, Integer> {
2222

2323
/**
2424
* Task 8.4: Implement findByTitleStartingWith(String prefix) method

examples/spring-boot-demo/implementation/src/main/java/com/example/demo/service/FilmService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import org.springframework.stereotype.Service;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.Objects;
910
import java.util.HashMap;
10-
import java.util.stream.Collectors;
1111

1212
/**
1313
* FilmService - Business Logic Layer for Film Query Operations
@@ -50,7 +50,7 @@ public List<Map<String, Object>> findFilmsByStartingLetter(String letter) {
5050

5151
// Task 6.3: Add business validation for letter parameter (already done in controller)
5252
// Task 6.4: Implement film filtering logic (case insensitive LIKE query)
53-
if (letter != null && !letter.trim().isEmpty()) {
53+
if (Objects.nonNull(letter) && !letter.trim().isEmpty()) {
5454
// Get films starting with the specified letter (case insensitive)
5555
films = filmRepository.findByTitleStartingWith(letter.trim());
5656
} else {
@@ -62,7 +62,7 @@ public List<Map<String, Object>> findFilmsByStartingLetter(String letter) {
6262
// Transform Film entities to Map<String, Object> format expected by controller
6363
return films.stream()
6464
.map(this::filmToMap)
65-
.collect(Collectors.toList());
65+
.toList();
6666
}
6767

6868
/**

0 commit comments

Comments
 (0)