View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.app.web.api.admin.scheduler;
17  
18  import static org.codelibs.fess.app.web.admin.scheduler.AdminSchedulerAction.getScheduledJob;
19  
20  import java.util.List;
21  import java.util.Map;
22  import java.util.UUID;
23  import java.util.stream.Collectors;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.codelibs.fess.Constants;
28  import org.codelibs.fess.app.pager.SchedulerPager;
29  import org.codelibs.fess.app.service.ScheduledJobService;
30  import org.codelibs.fess.app.web.CrudMode;
31  import org.codelibs.fess.app.web.api.ApiResult;
32  import org.codelibs.fess.app.web.api.ApiResult.ApiResponse;
33  import org.codelibs.fess.app.web.api.ApiResult.Status;
34  import org.codelibs.fess.app.web.api.admin.FessApiAdminAction;
35  import org.codelibs.fess.opensearch.config.exentity.ScheduledJob;
36  import org.lastaflute.web.Execute;
37  import org.lastaflute.web.response.HtmlResponse;
38  import org.lastaflute.web.response.JsonResponse;
39  
40  import jakarta.annotation.Resource;
41  
42  /**
43   * API action for admin scheduler management.
44   */
45  public class ApiAdminSchedulerAction extends FessApiAdminAction {
46  
47      /** The logger for this class. */
48      private static final Logger logger = LogManager.getLogger(ApiAdminSchedulerAction.class);
49  
50      // ===================================================================================
51      //                                                                         Constructor
52      //                                                                         ===========
53      /**
54       * Default constructor.
55       */
56      public ApiAdminSchedulerAction() {
57          super();
58      }
59  
60      // ===================================================================================
61      //                                                                           Attribute
62      //                                                                           =========
63  
64      /** The scheduled job service for managing scheduler settings. */
65      @Resource
66      private ScheduledJobService scheduledJobService;
67  
68      /**
69       * Index page (not supported for API).
70       *
71       * @return throws UnsupportedOperationException
72       */
73      @Execute
74      public HtmlResponse index() {
75          throw new UnsupportedOperationException("index() is not supported in API. Use the admin UI instead.");
76      }
77  
78      /**
79       * Starts a scheduled job by ID.
80       * When job logging is enabled, a pre-generated job log ID is returned in the response
81       * as {@code jobLogId}. When job logging is disabled, {@code jobLogId} is {@code null}.
82       *
83       * @param id the ID of the scheduled job to start
84       * @return JSON response with {@code jobLogId} (nullable) and status
85       */
86      // PUT /api/admin/scheduler/{id}/start
87      @Execute(urlPattern = "{}/@word")
88      public JsonResponse<ApiResult> put$start(final String id) {
89          final String[] jobLogId = { null };
90          scheduledJobService.getScheduledJob(id).ifPresent(entity -> {
91              if (!entity.isEnabled() || entity.isRunning()) {
92                  throwValidationErrorApi(messages -> {
93                      messages.addErrorsFailedToStartJob(GLOBAL, entity.getName());
94                  });
95              }
96              try {
97                  if (entity.isLoggingEnabled()) {
98                      jobLogId[0] = UUID.randomUUID().toString().replace("-", "");
99                      entity.start(Map.of(Constants.JOB_LOG_ID, jobLogId[0]));
100                 } else {
101                     entity.start();
102                 }
103             } catch (final Exception e) {
104                 throwValidationErrorApi(messages -> {
105                     messages.addErrorsFailedToStartJob(GLOBAL, entity.getName());
106                 });
107             }
108         }).orElse(() -> {
109             throwValidationErrorApi(messages -> {
110                 messages.addErrorsFailedToStartJob(GLOBAL, id);
111             });
112         });
113         return asJson(new ApiResult.ApiStartJobResponse().jobLogId(jobLogId[0]).status(Status.OK).result());
114     }
115 
116     /**
117      * Stops a scheduled job by ID.
118      *
119      * @param id the ID of the scheduled job to stop
120      * @return JSON response indicating success or failure
121      */
122     // PUT /api/admin/scheduler/{id}/stop
123     @Execute(urlPattern = "{}/@word")
124     public JsonResponse<ApiResult> put$stop(final String id) {
125         scheduledJobService.getScheduledJob(id).ifPresent(entity -> {
126             try {
127                 entity.stop();
128             } catch (final Exception e) {
129                 logger.warn("Failed to process a request.", e);
130                 throwValidationErrorApi(messages -> {
131                     messages.addErrorsFailedToStopJob(GLOBAL, entity.getName());
132                 });
133             }
134         }).orElse(() -> {
135             throwValidationErrorApi(messages -> {
136                 messages.addErrorsFailedToStartJob(GLOBAL, id);
137             });
138         });
139         return asJson(new ApiResponse().status(Status.OK).result());
140     }
141 
142     /**
143      * Retrieves scheduler settings with pagination.
144      *
145      * @param body the search parameters for filtering and pagination
146      * @return JSON response containing scheduler settings list
147      */
148     // GET /api/admin/scheduler
149     // PUT /api/admin/scheduler
150     @Execute
151     public JsonResponse<ApiResult> settings(final SearchBody body) {
152         validateApi(body, messages -> {});
153         final SchedulerPager pager = copyBeanToNewBean(body, SchedulerPager.class);
154         final List<ScheduledJob> list = scheduledJobService.getScheduledJobList(pager);
155         return asJson(
156                 new ApiResult.ApiConfigsResponse<EditBody>().settings(list.stream().map(this::createEditBody).collect(Collectors.toList()))
157                         .total(pager.getAllRecordCount())
158                         .status(ApiResult.Status.OK)
159                         .result());
160     }
161 
162     /**
163      * Retrieves a specific scheduler setting by ID.
164      *
165      * @param id the ID of the scheduler setting to retrieve
166      * @return JSON response containing the scheduler setting
167      */
168     // GET /api/admin/scheduler/setting/{id}
169     @Execute
170     public JsonResponse<ApiResult> get$setting(final String id) {
171         return asJson(new ApiResult.ApiConfigResponse()
172                 .setting(scheduledJobService.getScheduledJob(id).map(this::createEditBody).orElseGet(() -> {
173                     throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id));
174                     return null;
175                 }))
176                 .status(ApiResult.Status.OK)
177                 .result());
178     }
179 
180     /**
181      * Creates a new scheduler setting.
182      *
183      * @param body the scheduler data to create
184      * @return JSON response containing the created scheduler setting ID
185      */
186     // POST /api/admin/scheduler/setting
187     @Execute
188     public JsonResponse<ApiResult> post$setting(final CreateBody body) {
189         validateApi(body, messages -> {});
190         body.crudMode = CrudMode.CREATE;
191         final ScheduledJob entity = getScheduledJob(body).orElseGet(() -> {
192             throwValidationErrorApi(messages -> {
193                 messages.addErrorsCrudFailedToCreateInstance(GLOBAL);
194             });
195             return null;
196         });
197         try {
198             scheduledJobService.store(entity);
199             saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
200         } catch (final Exception e) {
201             logger.warn("Failed to process a request.", e);
202             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)));
203         }
204         return asJson(new ApiResult.ApiUpdateResponse().id(entity.getId()).created(true).status(ApiResult.Status.OK).result());
205     }
206 
207     /**
208      * Updates an existing scheduler setting.
209      *
210      * @param body the scheduler data to update
211      * @return JSON response containing the updated scheduler setting ID
212      */
213     // PUT /api/admin/scheduler/setting
214     @Execute
215     public JsonResponse<ApiResult> put$setting(final EditBody body) {
216         validateApi(body, messages -> {});
217         body.crudMode = CrudMode.EDIT;
218         final ScheduledJob entity = getScheduledJob(body).orElseGet(() -> {
219             throwValidationErrorApi(messages -> {
220                 messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, body.id);
221             });
222             return null;
223         });
224         try {
225             scheduledJobService.store(entity);
226         } catch (final Exception e) {
227             logger.warn("Failed to process a request.", e);
228             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)));
229         }
230         return asJson(new ApiResult.ApiUpdateResponse().id(entity.getId()).created(false).status(ApiResult.Status.OK).result());
231     }
232 
233     /**
234      * Deletes a scheduler setting by ID.
235      *
236      * @param id the ID of the scheduler setting to delete
237      * @return JSON response indicating success or failure
238      */
239     // DELETE /api/admin/scheduler/setting/{id}
240     @Execute
241     public JsonResponse<ApiResult> delete$setting(final String id) {
242         final ScheduledJob entity = scheduledJobService.getScheduledJob(id).orElseGet(() -> {
243             throwValidationErrorApi(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id));
244             return null;
245         });
246         try {
247             scheduledJobService.delete(entity);
248             saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
249         } catch (final Exception e) {
250             logger.warn("Failed to process a request.", e);
251             throwValidationErrorApi(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)));
252         }
253         return asJson(new ApiResult.ApiUpdateResponse().id(id).created(false).status(ApiResult.Status.OK).result());
254     }
255 
256     /**
257      * Creates an EditBody from a ScheduledJob entity.
258      *
259      * @param entity the scheduled job entity to convert
260      * @return the converted EditBody
261      */
262     protected EditBody createEditBody(final ScheduledJob entity) {
263         final EditBody body = new EditBody();
264         copyBeanToBean(entity, body, op -> op.exclude(Constants.COMMON_CONVERSION_RULE));
265         body.running = entity.isRunning();
266         return body;
267     }
268 
269 }