11package 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 ;
123import info .jab .ms .dto .FilmDTO ;
134import info .jab .ms .entity .Film ;
14-
5+ import info . jab . ms . service . FilmService ;
156import io .swagger .v3 .oas .annotations .Operation ;
167import io .swagger .v3 .oas .annotations .Parameter ;
8+ import io .swagger .v3 .oas .annotations .media .Content ;
9+ import io .swagger .v3 .oas .annotations .media .Schema ;
1710import io .swagger .v3 .oas .annotations .responses .ApiResponse ;
1811import io .swagger .v3 .oas .annotations .responses .ApiResponses ;
1912import 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-
2313import jakarta .servlet .http .HttpServletRequest ;
2414import java .net .URI ;
2515import java .time .Instant ;
26- import java .util .Map ;
27- import java .util .Objects ;
2816import java .util .HashMap ;
2917import 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