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