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.admin.keymatch;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.apache.logging.log4j.LogManager;
23  import org.apache.logging.log4j.Logger;
24  import org.codelibs.fess.Constants;
25  import org.codelibs.fess.annotation.Secured;
26  import org.codelibs.fess.app.pager.KeyMatchPager;
27  import org.codelibs.fess.app.service.KeyMatchService;
28  import org.codelibs.fess.app.web.CrudMode;
29  import org.codelibs.fess.app.web.base.FessAdminAction;
30  import org.codelibs.fess.helper.KeyMatchHelper;
31  import org.codelibs.fess.helper.SystemHelper;
32  import org.codelibs.fess.opensearch.config.exentity.KeyMatch;
33  import org.codelibs.fess.util.ComponentUtil;
34  import org.codelibs.fess.util.RenderDataUtil;
35  import org.dbflute.optional.OptionalEntity;
36  import org.dbflute.optional.OptionalThing;
37  import org.lastaflute.web.Execute;
38  import org.lastaflute.web.response.HtmlResponse;
39  import org.lastaflute.web.response.render.RenderData;
40  import org.lastaflute.web.ruts.process.ActionRuntime;
41  
42  import jakarta.annotation.Resource;
43  
44  /**
45   * Admin action for Key Match management.
46   *
47   */
48  public class AdminKeymatchAction extends FessAdminAction {
49  
50      /**
51       * Default constructor.
52       */
53      public AdminKeymatchAction() {
54          super();
55      }
56  
57      /** The role name for key match administration. */
58      public static final String ROLE = "admin-keymatch";
59  
60      /** Logger for this class. */
61      private static final Logger logger = LogManager.getLogger(AdminKeymatchAction.class);
62  
63      // ===================================================================================
64      //                                                                           Attribute
65      //                                                                           =========
66      /** Helper for key match operations. */
67      @Resource
68      private KeyMatchHelper keyMatchHelper;
69  
70      /** Service for key match operations. */
71      @Resource
72      private KeyMatchService keyMatchService;
73  
74      /** Pager for key match list pagination. */
75      @Resource
76      private KeyMatchPager keyMatchPager;
77  
78      // ===================================================================================
79      //                                                                               Hook
80      //                                                                              ======
81      /**
82       * Sets up HTML data for rendering, including help link.
83       *
84       * @param runtime the action runtime
85       */
86      @Override
87      protected void setupHtmlData(final ActionRuntime runtime) {
88          super.setupHtmlData(runtime);
89          runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameKeymatch()));
90      }
91  
92      /**
93       * Returns the action role for this admin action.
94       *
95       * @return the role name
96       */
97      @Override
98      protected String getActionRole() {
99          return ROLE;
100     }
101 
102     // ===================================================================================
103     //                                                                      Search Execute
104     //                                                                      ==============
105     /**
106      * Displays the key match list page.
107      *
108      * @param form the search form
109      * @return HTML response for the list page
110      */
111     @Execute
112     @Secured({ ROLE, ROLE + VIEW })
113     public HtmlResponse index(final SearchForm form) {
114         return asListHtml();
115     }
116 
117     /**
118      * Displays the key match list with pagination.
119      *
120      * @param pageNumber the page number
121      * @param form the search form
122      * @return HTML response for the list page
123      */
124     @Execute
125     @Secured({ ROLE, ROLE + VIEW })
126     public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
127         pageNumber.ifPresent(num -> {
128             keyMatchPager.setCurrentPageNumber(pageNumber.get());
129         }).orElse(() -> {
130             keyMatchPager.setCurrentPageNumber(0);
131         });
132         return asHtml(path_AdminKeymatch_AdminKeymatchJsp).renderWith(data -> {
133             searchPaging(data, form);
134         });
135     }
136 
137     /**
138      * Searches key matches based on the form criteria.
139      *
140      * @param form the search form
141      * @return HTML response for the search results
142      */
143     @Execute
144     @Secured({ ROLE, ROLE + VIEW })
145     public HtmlResponse search(final SearchForm form) {
146         copyBeanToBean(form, keyMatchPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
147         return asHtml(path_AdminKeymatch_AdminKeymatchJsp).renderWith(data -> {
148             searchPaging(data, form);
149         });
150     }
151 
152     /**
153      * Resets the search criteria and displays the default list.
154      *
155      * @param form the search form
156      * @return HTML response for the reset list
157      */
158     @Execute
159     @Secured({ ROLE, ROLE + VIEW })
160     public HtmlResponse reset(final SearchForm form) {
161         keyMatchPager.clear();
162         return asHtml(path_AdminKeymatch_AdminKeymatchJsp).renderWith(data -> {
163             searchPaging(data, form);
164         });
165     }
166 
167     /**
168      * Sets up data for search result pagination.
169      *
170      * @param data the render data
171      * @param form the search form
172      */
173     protected void searchPaging(final RenderData data, final SearchForm form) {
174         RenderDataUtil.register(data, "keyMatchItems", keyMatchService.getKeyMatchList(keyMatchPager)); // page navi
175 
176         // restore from pager
177         copyBeanToBean(keyMatchPager, form, op -> op.include("term", "query"));
178     }
179 
180     // ===================================================================================
181     //                                                                        Edit Execute
182     //                                                                        ============
183     // -----------------------------------------------------
184     //                                            Entry Page
185     //                                            ----------
186     /**
187      * Displays the create new key match page.
188      *
189      * @return HTML response for the create page
190      */
191     @Execute
192     @Secured({ ROLE })
193     public HtmlResponse createnew() {
194         saveToken();
195         return asHtml(path_AdminKeymatch_AdminKeymatchEditJsp).useForm(CreateForm.class, op -> {
196             op.setup(form -> {
197                 form.initialize();
198                 form.crudMode = CrudMode.CREATE;
199             });
200         });
201     }
202 
203     /**
204      * Displays the edit key match page.
205      *
206      * @param form the edit form
207      * @return HTML response for the edit page
208      */
209     @Execute
210     @Secured({ ROLE })
211     public HtmlResponse edit(final EditForm form) {
212         validate(form, messages -> {}, this::asListHtml);
213         final String id = form.id;
214         keyMatchService.getKeyMatch(id).ifPresent(entity -> {
215             copyBeanToBean(entity, form, op -> {});
216         }).orElse(() -> {
217             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml);
218         });
219         saveToken();
220         if (form.crudMode.intValue() == CrudMode.EDIT) {
221             // back
222             form.crudMode = CrudMode.DETAILS;
223             return asDetailsHtml();
224         }
225         form.crudMode = CrudMode.EDIT;
226         return asEditHtml();
227     }
228 
229     // -----------------------------------------------------
230     //                                               Details
231     //                                               -------
232     /**
233      * Displays the key match details page with boosted documents.
234      *
235      * @param crudMode the CRUD mode
236      * @param id the key match ID
237      * @return HTML response for the details page
238      */
239     @Execute
240     @Secured({ ROLE, ROLE + VIEW })
241     public HtmlResponse details(final int crudMode, final String id) {
242         verifyCrudMode(crudMode, CrudMode.DETAILS, this::asListHtml);
243         saveToken();
244         final List<Map<String, Object>> docList = new ArrayList<>();
245         return asHtml(path_AdminKeymatch_AdminKeymatchDetailsJsp).useForm(EditForm.class, op -> {
246             op.setup(form -> {
247                 keyMatchService.getKeyMatch(id).ifPresent(entity -> {
248                     copyBeanToBean(entity, form, copyOp -> {
249                         copyOp.excludeNull();
250                     });
251                     form.crudMode = crudMode;
252                     docList.addAll(keyMatchHelper.getBoostedDocumentList(entity));
253                 }).orElse(() -> {
254                     throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml);
255                 });
256             });
257         }).renderWith(data -> {
258             data.register("docs", docList);
259         });
260     }
261 
262     // -----------------------------------------------------
263     //                                         Actually Crud
264     //                                         -------------
265     /**
266      * Creates a new key match.
267      *
268      * @param form the create form
269      * @return HTML response after creation
270      */
271     @Execute
272     @Secured({ ROLE })
273     public HtmlResponse create(final CreateForm form) {
274         verifyCrudMode(form.crudMode, CrudMode.CREATE, this::asListHtml);
275         validate(form, messages -> {}, this::asEditHtml);
276         verifyToken(this::asEditHtml);
277         getKeyMatch(form).ifPresent(entity -> {
278             try {
279                 keyMatchService.store(entity);
280                 saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
281                 ComponentUtil.getKeyMatchHelper().update();
282             } catch (final Exception e) {
283                 logger.warn("Failed to process a request.", e);
284                 throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)),
285                         this::asEditHtml);
286             }
287         }).orElse(() -> {
288             throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), this::asEditHtml);
289         });
290         return redirect(getClass());
291     }
292 
293     /**
294      * Updates an existing key match.
295      *
296      * @param form the edit form
297      * @return HTML response after update
298      */
299     @Execute
300     @Secured({ ROLE })
301     public HtmlResponse update(final EditForm form) {
302         verifyCrudMode(form.crudMode, CrudMode.EDIT, this::asListHtml);
303         validate(form, messages -> {}, this::asEditHtml);
304         verifyToken(this::asEditHtml);
305         getKeyMatch(form).ifPresent(entity -> {
306             try {
307                 keyMatchService.store(entity);
308                 saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
309                 ComponentUtil.getKeyMatchHelper().update();
310             } catch (final Exception e) {
311                 logger.warn("Failed to process a request.", e);
312                 throwValidationError(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)),
313                         this::asEditHtml);
314             }
315         }).orElse(() -> {
316             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), this::asEditHtml);
317         });
318         return redirect(getClass());
319     }
320 
321     /**
322      * Deletes a key match.
323      *
324      * @param form the edit form
325      * @return HTML response after deletion
326      */
327     @Execute
328     @Secured({ ROLE })
329     public HtmlResponse delete(final EditForm form) {
330         verifyCrudMode(form.crudMode, CrudMode.DETAILS, this::asListHtml);
331         validate(form, messages -> {}, this::asDetailsHtml);
332         verifyToken(this::asDetailsHtml);
333         final String id = form.id;
334         keyMatchService.getKeyMatch(id).ifPresent(entity -> {
335             try {
336                 keyMatchService.delete(entity);
337                 saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
338                 ComponentUtil.getKeyMatchHelper().update();
339             } catch (final Exception e) {
340                 logger.warn("Failed to process a request.", e);
341                 throwValidationError(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)),
342                         this::asEditHtml);
343             }
344         }).orElse(() -> {
345             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asDetailsHtml);
346         });
347         return redirect(getClass());
348     }
349 
350     // ===================================================================================
351     //                                                                        Assist Logic
352     //                                                                        ============
353 
354     /**
355      * Gets a key match entity based on the form and current user info.
356      *
357      * @param form the create form
358      * @param username the current username
359      * @param currentTime the current time
360      * @return optional key match entity
361      */
362     public static OptionalEntity<KeyMatch> getEntity(final CreateForm form, final String username, final long currentTime) {
363         switch (form.crudMode) {
364         case CrudMode.CREATE:
365             return OptionalEntity.of(new KeyMatch()).map(entity -> {
366                 entity.setCreatedBy(username);
367                 entity.setCreatedTime(currentTime);
368                 return entity;
369             });
370         case CrudMode.EDIT:
371             if (form instanceof EditForm) {
372                 return ComponentUtil.getComponent(KeyMatchService.class).getKeyMatch(((EditForm) form).id);
373             }
374             break;
375         default:
376             break;
377         }
378         return OptionalEntity.empty();
379     }
380 
381     /**
382      * Gets a key match entity from the form with system info.
383      *
384      * @param form the create form
385      * @return optional key match entity
386      */
387     public static OptionalEntity<KeyMatch> getKeyMatch(final CreateForm form) {
388         final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
389         final String username = systemHelper.getUsername();
390         final long currentTime = systemHelper.getCurrentTimeAsLong();
391         return getEntity(form, username, currentTime).map(entity -> {
392             entity.setUpdatedBy(username);
393             entity.setUpdatedTime(currentTime);
394             copyBeanToBean(form, entity, op -> op.exclude(Constants.COMMON_CONVERSION_RULE));
395             return entity;
396         });
397     }
398 
399     // ===================================================================================
400     //                                                                        Small Helper
401     //                                                                        ============
402     //                                                                              JSP
403     //                                                                           =========
404 
405     /**
406      * Returns HTML response for the list page.
407      *
408      * @return HTML response for the list page
409      */
410     private HtmlResponse asListHtml() {
411         return asHtml(path_AdminKeymatch_AdminKeymatchJsp).renderWith(data -> {
412             RenderDataUtil.register(data, "keyMatchItems", keyMatchService.getKeyMatchList(keyMatchPager)); // page navi
413         }).useForm(SearchForm.class, setup -> {
414             setup.setup(form -> {
415                 copyBeanToBean(keyMatchPager, form, op -> op.include("term", "query"));
416             });
417         });
418     }
419 
420     /**
421      * Returns HTML response for the edit page.
422      *
423      * @return HTML response for the edit page
424      */
425     private HtmlResponse asEditHtml() {
426         return asHtml(path_AdminKeymatch_AdminKeymatchEditJsp);
427     }
428 
429     /**
430      * Returns HTML response for the details page.
431      *
432      * @return HTML response for the details page
433      */
434     private HtmlResponse asDetailsHtml() {
435         return asHtml(path_AdminKeymatch_AdminKeymatchDetailsJsp);
436     }
437 }