View Javadoc
1   package com.acumenvelocity.ath.controller;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.stream.Collectors;
6   
7   import com.acumenvelocity.ath.common.ControllerUtil;
8   import com.acumenvelocity.ath.common.Response;
9   import com.acumenvelocity.ath.model.EncodingInfo;
10  import com.acumenvelocity.ath.model.EncodingInfosWrapper;
11  import com.acumenvelocity.ath.model.FilterInfo;
12  import com.acumenvelocity.ath.model.FilterInfosWrapper;
13  import com.acumenvelocity.ath.model.LanguageInfo;
14  import com.acumenvelocity.ath.model.LanguageInfosWrapper;
15  import com.acumenvelocity.ath.model.PaginationInfo;
16  
17  import io.swagger.oas.inflector.models.RequestContext;
18  import io.swagger.oas.inflector.models.ResponseContext;
19  
20  /**
21   * REST Controller for handling Okapi framework related operations.
22   * Provides endpoints for retrieving language, encoding, and filter information
23   * from the Okapi framework with pagination support.
24   * 
25   * <p>
26   * This controller integrates with Swagger/OpenAPI for API documentation
27   * and follows RESTful best practices.
28   * </p>
29   * 
30   * @author Acumen Velocity
31   * @version 1.0
32   * @since 1.0
33   */
34  public class OkapiController {
35  
36    /**
37     * Retrieves a paginated list of available languages with 2-character ISO codes only.
38     *
39     * <p>
40     * <b>Pagination Details:</b>
41     * </p>
42     * <ul>
43     * <li>Default page size: 10 items</li>
44     * <li>Maximum page size: 100 items</li>
45     * <li>Page numbers start from 1</li>
46     * <li>Automatically adjusts invalid page numbers</li>
47     * </ul>
48     *
49     * <p>
50     * <b>Language Code Filter:</b>
51     * Only languages with 2-character ISO 639-1 codes are returned (e.g., "en", "fr", "de").
52     * Languages with 3-character codes or regional variants (e.g., "ace", "en-US") are excluded.
53     * </p>
54     *
55     * @param request  the HTTP request context provided by Swagger Inflector
56     * @param page     the requested page number (1-based, optional)
57     * @param pageSize the number of items per page (1-100, optional)
58     * @return ResponseContext containing paginated language list or error details
59     *
60     * @apiNote GET /languages
61     */
62    public ResponseContext getLanguages(RequestContext request, Integer page, Integer pageSize) {
63      try {
64        // Retrieve and filter languages (2-letter codes only)
65        List<LanguageInfo> filteredLanguages = ControllerUtil.getLanguageInfos().stream()
66            .filter(lang -> {
67              String code = lang.getIsoCode();
68              return code != null && code.length() == 2 && !code.contains("-");
69            })
70            .collect(Collectors.toList());
71  
72        LanguageInfosWrapper wrapper = new LanguageInfosWrapper();
73  
74        // Empty result case
75        if (filteredLanguages.isEmpty()) {
76          wrapper.languages(new ArrayList<>())
77              .pagination(new PaginationInfo()
78                  .page(1)
79                  .pageSize(0)
80                  .totalItems(0L)
81                  .totalPages(0)
82                  .hasNext(false)
83                  .hasPrevious(false));
84          return Response.success(200, wrapper);
85        }
86  
87        // Determine pagination parameters
88        long totalItems = filteredLanguages.size();
89        int size = (pageSize != null) ? Math.max(1, Math.min(100, pageSize)) : (int) totalItems;      
90        int totalPages = (int) Math.ceil((double) totalItems / size);
91  
92        int pageNum;
93        List<LanguageInfo> resultLanguages;
94  
95        if (page == null && pageSize == null) {
96          // No pagination requested → return all as "page 1"
97          pageNum = 1;
98          resultLanguages = filteredLanguages;
99          totalPages = 1;
100       } else {
101         // Pagination requested
102         pageNum = (page != null) ? Math.max(1, Math.min(page, Math.max(1, totalPages))) : 1;
103         int start = (pageNum - 1) * size;
104         int end = Math.min(start + size, filteredLanguages.size());
105         resultLanguages = filteredLanguages.subList(start, end);
106       }
107 
108       // Fluent construction — exactly like your preferred style
109       PaginationInfo pagination = new PaginationInfo()
110           .page(pageNum)
111           .pageSize(size)
112           .totalItems(totalItems)
113           .totalPages(totalPages)
114           .hasNext(pageNum < totalPages)
115           .hasPrevious(pageNum > 1);
116 
117       wrapper.languages(resultLanguages)
118           .pagination(pagination);
119 
120       return Response.success(200, wrapper);
121 
122     } catch (Exception e) {
123       return Response.error(500, e, "Error fetching languages");
124     }
125   }
126 
127   /**
128    * Retrieves a paginated list of available languages from Okapi framework.
129    * 
130    * <p>
131    * <b>Pagination Details:</b>
132    * </p>
133    * <ul>
134    * <li>Default page size: 10 items</li>
135    * <li>Maximum page size: 100 items</li>
136    * <li>Page numbers start from 1</li>
137    * <li>Automatically adjusts invalid page numbers</li>
138    * </ul>
139    *
140    * @param request  the HTTP request context provided by Swagger Inflector
141    * @param page     the requested page number (1-based, optional)
142    * @param pageSize the number of items per page (1-100, optional)
143    * @return ResponseContext containing paginated locale list or error details
144    * 
145    * @apiNote GET /locales
146    */
147   public ResponseContext getLocales(RequestContext request, Integer page, Integer pageSize) {
148     try {
149       List<LanguageInfo> allLanguages = ControllerUtil.getLanguageInfos();
150       LanguageInfosWrapper wrapper = new LanguageInfosWrapper();
151 
152       // Empty result
153       if (allLanguages.isEmpty()) {
154         wrapper.languages(new ArrayList<>())
155             .pagination(new PaginationInfo()
156                 .page(1)
157                 .pageSize(0)
158                 .totalItems(0L)
159                 .totalPages(0)
160                 .hasNext(false)
161                 .hasPrevious(false));
162 
163         return Response.success(200, wrapper);
164       }
165 
166       // Default page size
167       long totalItems = allLanguages.size();
168       int size = (pageSize != null) ? Math.max(1, Math.min(100, pageSize)) : (int) totalItems;      
169       int totalPages = (int) Math.ceil(totalItems / (double) size);
170 
171       int pageNum;
172       List<LanguageInfo> resultLanguages;
173 
174       if (page == null && pageSize == null) {
175         // No pagination requested → return everything as page 1
176         pageNum = 1;
177         resultLanguages = allLanguages;
178         totalPages = 1;
179       } else {
180         // Pagination requested
181         pageNum = (page != null) ? Math.max(1, Math.min(page, Math.max(1, totalPages))) : 1;
182         int start = (pageNum - 1) * size;
183         int end = Math.min(start + size, allLanguages.size());
184         resultLanguages = allLanguages.subList(start, end);
185       }
186 
187       // Fluent construction — exactly your preferred style
188       PaginationInfo pagination = new PaginationInfo()
189           .page(pageNum)
190           .pageSize(size)
191           .totalItems(totalItems)
192           .totalPages(totalPages)
193           .hasNext(pageNum < totalPages)
194           .hasPrevious(pageNum > 1);
195 
196       wrapper.languages(resultLanguages)
197           .pagination(pagination);
198 
199       return Response.success(200, wrapper);
200 
201     } catch (Exception e) {
202       return Response.error(500, e, "Error fetching locales");
203     }
204   }
205 
206   /**
207    * Retrieves a paginated list of available encodings from Okapi framework.
208    * 
209    * <p>
210    * Follows the same pagination logic as {@link #getLanguages(RequestContext, Integer, Integer)}.
211    * </p>
212    *
213    * @param request  the HTTP request context provided by Swagger Inflector
214    * @param page     the requested page number (1-based, optional)
215    * @param pageSize the number of items per page (1-100, optional)
216    * @return ResponseContext containing paginated encoding list or error details
217    * 
218    * @apiNote GET /encodings
219    */
220   public ResponseContext getEncodings(RequestContext request, Integer page, Integer pageSize) {
221     try {
222       List<EncodingInfo> allEncodings = ControllerUtil.getEncodingInfos();
223       EncodingInfosWrapper wrapper = new EncodingInfosWrapper();
224 
225       // Empty result
226       if (allEncodings.isEmpty()) {
227         wrapper.encodings(new ArrayList<>())
228             .pagination(new PaginationInfo()
229                 .page(1)
230                 .pageSize(0)
231                 .totalItems(0L)
232                 .totalPages(0)
233                 .hasNext(false)
234                 .hasPrevious(false));
235         
236         return Response.success(200, wrapper);
237       }
238 
239       // Page size with bounds
240       long totalItems = allEncodings.size();
241       int size = (pageSize != null) ? Math.max(1, Math.min(100, pageSize)) : (int) totalItems;      
242       int totalPages = (int) Math.ceil(totalItems / (double) size);
243 
244       int pageNum;
245       List<EncodingInfo> resultEncodings;
246 
247       if (page == null && pageSize == null) {
248         // No pagination → return all as page 1
249         pageNum = 1;
250         resultEncodings = allEncodings;
251         totalPages = 1;
252       } else {
253         // Pagination requested
254         pageNum = (page != null) ? Math.max(1, Math.min(page, Math.max(1, totalPages))) : 1;
255         int start = (pageNum - 1) * size;
256         int end = Math.min(start + size, allEncodings.size());
257         resultEncodings = allEncodings.subList(start, end);
258       }
259 
260       // Fluent pagination object (your preferred style)
261       PaginationInfo pagination = new PaginationInfo()
262           .page(pageNum)
263           .pageSize(size)
264           .totalItems(totalItems)
265           .totalPages(totalPages)
266           .hasNext(pageNum < totalPages)
267           .hasPrevious(pageNum > 1);
268 
269       // Fluent wrapper population
270       wrapper.encodings(resultEncodings)
271           .pagination(pagination);
272 
273       return Response.success(200, wrapper);
274 
275     } catch (Exception e) {
276       return Response.error(500, e, "Error fetching encodings");
277     }
278   }
279 
280   /**
281    * Retrieves a paginated list of available filter configurations from Okapi framework.
282    * 
283    * <p>
284    * Follows the same pagination logic as {@link #getLanguages(RequestContext, Integer, Integer)}.
285    * </p>
286    *
287    * @param request  the HTTP request context provided by Swagger Inflector
288    * @param page     the requested page number (1-based, optional)
289    * @param pageSize the number of items per page (1-100, optional)
290    * @return ResponseContext containing paginated filter list or error details
291    * 
292    * @apiNote GET /filters
293    */
294   public ResponseContext getFilters(RequestContext request, Integer page, Integer pageSize) {
295     try {
296       List<FilterInfo> allFilters = ControllerUtil.getFilterInfos();
297       FilterInfosWrapper wrapper = new FilterInfosWrapper();
298 
299       // Empty result
300       if (allFilters.isEmpty()) {
301         wrapper.filters(new ArrayList<>())
302             .pagination(new PaginationInfo()
303                 .page(1)
304                 .pageSize(0)
305                 .totalItems(0L)
306                 .totalPages(0)
307                 .hasNext(false)
308                 .hasPrevious(false));
309         
310         return Response.success(200, wrapper);
311       }
312 
313       // Page size (1–100)
314       long totalItems = allFilters.size();
315       int size = (pageSize != null) ? Math.max(1, Math.min(100, pageSize)) : (int) totalItems;      
316       int totalPages = (int) Math.ceil(totalItems / (double) size);
317 
318       int pageNum;
319       List<FilterInfo> resultFilters;
320 
321       if (page == null && pageSize == null) {
322         // No pagination → return all as page 1
323         pageNum = 1;
324         resultFilters = allFilters;
325         totalPages = 1;
326       } else {
327         // Pagination requested
328         pageNum = (page != null) ? Math.max(1, Math.min(page, Math.max(1, totalPages))) : 1;
329         int start = (pageNum - 1) * size;
330         int end = Math.min(start + size, allFilters.size());
331         resultFilters = allFilters.subList(start, end);
332       }
333 
334       // Fluent PaginationInfo (your preferred style)
335       PaginationInfo pagination = new PaginationInfo()
336           .page(pageNum)
337           .pageSize(size)
338           .totalItems(totalItems)
339           .totalPages(totalPages)
340           .hasNext(pageNum < totalPages)
341           .hasPrevious(pageNum > 1);
342 
343       // Fluent wrapper
344       wrapper.filters(resultFilters)
345           .pagination(pagination);
346 
347       return Response.success(200, wrapper);
348 
349     } catch (Exception e) {
350       return Response.error(500, e, "Error fetching filters");
351     }
352   }
353 }