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.dict.protwords;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  
22  import org.apache.logging.log4j.LogManager;
23  import org.apache.logging.log4j.Logger;
24  import org.codelibs.core.beans.util.BeanUtil;
25  import org.codelibs.core.lang.StringUtil;
26  import org.codelibs.fess.Constants;
27  import org.codelibs.fess.annotation.Secured;
28  import org.codelibs.fess.app.pager.ProtwordsPager;
29  import org.codelibs.fess.app.service.ProtwordsService;
30  import org.codelibs.fess.app.web.CrudMode;
31  import org.codelibs.fess.app.web.admin.dict.AdminDictAction;
32  import org.codelibs.fess.app.web.base.FessAdminAction;
33  import org.codelibs.fess.app.web.base.FessBaseAction;
34  import org.codelibs.fess.dict.protwords.ProtwordsItem;
35  import org.codelibs.fess.util.ComponentUtil;
36  import org.codelibs.fess.util.RenderDataUtil;
37  import org.dbflute.optional.OptionalEntity;
38  import org.dbflute.optional.OptionalThing;
39  import org.lastaflute.web.Execute;
40  import org.lastaflute.web.response.ActionResponse;
41  import org.lastaflute.web.response.HtmlResponse;
42  import org.lastaflute.web.response.render.RenderData;
43  import org.lastaflute.web.ruts.process.ActionRuntime;
44  import org.lastaflute.web.validation.VaErrorHook;
45  import org.lastaflute.web.validation.exception.ValidationErrorException;
46  
47  import jakarta.annotation.Resource;
48  
49  /**
50   * Admin action for Protected Words management.
51   *
52   */
53  public class AdminDictProtwordsAction extends FessAdminAction {
54  
55      /**
56       * Default constructor.
57       */
58      public AdminDictProtwordsAction() {
59          super();
60      }
61  
62      /** The role for this action. */
63      public static final String ROLE = "admin-dict";
64  
65      private static final Logger logger = LogManager.getLogger(AdminDictProtwordsAction.class);
66  
67      // ===================================================================================
68      //                                                                           Attribute
69      //                                                                           =========
70      @Resource
71      private ProtwordsService protwordsService;
72      @Resource
73      private ProtwordsPager protwordsPager;
74  
75      // ===================================================================================
76      //                                                                               Hook
77      //                                                                              ======
78      @Override
79      protected void setupHtmlData(final ActionRuntime runtime) {
80          super.setupHtmlData(runtime);
81          runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameDictProtwords()));
82      }
83  
84      @Override
85      protected String getActionRole() {
86          return ROLE;
87      }
88  
89      // ===================================================================================
90      //                                                                      Search Execute
91      //                                                                      ==============
92      /**
93       * Show the index page.
94       * @param form The search form.
95       * @return The HTML response.
96       */
97      @Execute
98      @Secured({ ROLE, ROLE + VIEW })
99      public HtmlResponse index(final SearchForm form) {
100         validate(form, messages -> {}, this::asDictIndexHtml);
101         protwordsPager.clear();
102         return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
103             searchPaging(data, form);
104         });
105     }
106 
107     /**
108      * Show the list page.
109      * @param pageNumber The page number.
110      * @param form The search form.
111      * @return The HTML response.
112      */
113     @Execute
114     @Secured({ ROLE, ROLE + VIEW })
115     public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
116         validate(form, messages -> {}, this::asDictIndexHtml);
117         pageNumber.ifPresent(num -> {
118             protwordsPager.setCurrentPageNumber(pageNumber.get());
119         }).orElse(() -> {
120             protwordsPager.setCurrentPageNumber(0);
121         });
122         return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
123             searchPaging(data, form);
124         });
125     }
126 
127     /**
128      * Search the protected words.
129      * @param form The search form.
130      * @return The HTML response.
131      */
132     @Execute
133     @Secured({ ROLE, ROLE + VIEW })
134     public HtmlResponse search(final SearchForm form) {
135         validate(form, messages -> {}, this::asDictIndexHtml);
136         copyBeanToBean(form, protwordsPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
137         return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
138             searchPaging(data, form);
139         });
140     }
141 
142     /**
143      * Reset the search form.
144      * @param form The search form.
145      * @return The HTML response.
146      */
147     @Execute
148     @Secured({ ROLE, ROLE + VIEW })
149     public HtmlResponse reset(final SearchForm form) {
150         validate(form, messages -> {}, this::asDictIndexHtml);
151         protwordsPager.clear();
152         return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
153             searchPaging(data, form);
154         });
155     }
156 
157     /**
158      * Search with paging.
159      * @param data The render data.
160      * @param form The search form.
161      */
162     protected void searchPaging(final RenderData data, final SearchForm form) {
163         // page navi
164         RenderDataUtil.register(data, "protwordsItemItems", protwordsService.getProtwordsList(form.dictId, protwordsPager));
165 
166         // restore from pager
167         BeanUtil.copyBeanToBean(protwordsPager, form, op -> {
168             op.exclude(Constants.PAGER_CONVERSION_RULE);
169         });
170     }
171 
172     // ===================================================================================
173     //                                                                        Edit Execute
174     //                                                                        ============
175     // -----------------------------------------------------
176     //                                            Entry Page
177     //                                            ----------
178     /**
179      * Show the create new page.
180      * @param dictId The dictionary ID.
181      * @return The HTML response.
182      */
183     @Execute
184     @Secured({ ROLE })
185     public HtmlResponse createnew(final String dictId) {
186         saveToken();
187         return asHtml(path_AdminDictProtwords_AdminDictProtwordsEditJsp).useForm(CreateForm.class, op -> {
188             op.setup(form -> {
189                 form.initialize();
190                 form.crudMode = CrudMode.CREATE;
191                 form.dictId = dictId;
192             });
193         });
194     }
195 
196     /**
197      * Show the edit page.
198      * @param form The edit form.
199      * @return The HTML response.
200      */
201     @Execute
202     @Secured({ ROLE })
203     public HtmlResponse edit(final EditForm form) {
204         validate(form, messages -> {}, () -> asListHtml(form.dictId));
205         protwordsService.getProtwordsItem(form.dictId, form.id).ifPresent(entity -> {
206             form.input = entity.getInputValue();
207         }).orElse(() -> {
208             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
209                     () -> asListHtml(form.dictId));
210         });
211         saveToken();
212         if (form.crudMode.intValue() == CrudMode.EDIT) {
213             // back
214             form.crudMode = CrudMode.DETAILS;
215             return asDetailsHtml();
216         }
217         form.crudMode = CrudMode.EDIT;
218         return asEditHtml();
219     }
220 
221     // -----------------------------------------------------
222     //                                               Details
223     //                                               -------
224     /**
225      * Show the details page.
226      * @param dictId The dictionary ID.
227      * @param crudMode The CRUD mode.
228      * @param id The ID.
229      * @return The HTML response.
230      */
231     @Execute
232     @Secured({ ROLE, ROLE + VIEW })
233     public HtmlResponse details(final String dictId, final int crudMode, final long id) {
234         verifyCrudMode(crudMode, CrudMode.DETAILS, dictId);
235         saveToken();
236         return asDetailsHtml().useForm(EditForm.class, op -> {
237             op.setup(form -> {
238                 protwordsService.getProtwordsItem(dictId, id).ifPresent(entity -> {
239                     form.input = entity.getInputValue();
240                 }).orElse(() -> {
241                     throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, dictId + ":" + id),
242                             () -> asListHtml(dictId));
243                 });
244                 form.id = id;
245                 form.crudMode = crudMode;
246                 form.dictId = dictId;
247             });
248         });
249     }
250 
251     // -----------------------------------------------------
252     //                                              Download
253     //                                               -------
254     /**
255      * Show the download page.
256      * @param dictId The dictionary ID.
257      * @return The HTML response.
258      */
259     @Execute
260     @Secured({ ROLE, ROLE + VIEW })
261     public HtmlResponse downloadpage(final String dictId) {
262         saveToken();
263         return asHtml(path_AdminDictProtwords_AdminDictProtwordsDownloadJsp).useForm(DownloadForm.class, op -> {
264             op.setup(form -> {
265                 form.dictId = dictId;
266             });
267         }).renderWith(data -> {
268             protwordsService.getProtwordsFile(dictId).ifPresent(file -> {
269                 RenderDataUtil.register(data, "path", file.getPath());
270             }).orElse(() -> {
271                 throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL), this::asDictIndexHtml);
272             });
273         });
274     }
275 
276     /**
277      * Download the protected words.
278      * @param form The download form.
279      * @return The action response.
280      */
281     @Execute
282     @Secured({ ROLE, ROLE + VIEW })
283     public ActionResponse download(final DownloadForm form) {
284         validate(form, messages -> {}, () -> downloadpage(form.dictId));
285         verifyTokenKeep(() -> downloadpage(form.dictId));
286         return protwordsService.getProtwordsFile(form.dictId)
287                 .map(file -> asStream(new File(file.getPath()).getName()).contentTypeOctetStream().stream(out -> {
288                     file.writeOut(out);
289                 }))
290                 .orElseGet(() -> {
291                     throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL),
292                             () -> downloadpage(form.dictId));
293                     return null;
294                 });
295     }
296 
297     // -----------------------------------------------------
298     //                                                Upload
299     //                                               -------
300     /**
301      * Show the upload page.
302      * @param dictId The dictionary ID.
303      * @return The HTML response.
304      */
305     @Execute
306     @Secured({ ROLE })
307     public HtmlResponse uploadpage(final String dictId) {
308         saveToken();
309         return asHtml(path_AdminDictProtwords_AdminDictProtwordsUploadJsp).useForm(UploadForm.class, op -> {
310             op.setup(form -> {
311                 form.dictId = dictId;
312             });
313         }).renderWith(data -> {
314             protwordsService.getProtwordsFile(dictId).ifPresent(file -> {
315                 RenderDataUtil.register(data, "path", file.getPath());
316             }).orElse(() -> {
317                 throwValidationError(messages -> messages.addErrorsFailedToDownloadProtwordsFile(GLOBAL), this::asDictIndexHtml);
318             });
319         });
320     }
321 
322     /**
323      * Upload the protected words.
324      * @param form The upload form.
325      * @return The HTML response.
326      */
327     @Execute
328     @Secured({ ROLE })
329     public HtmlResponse upload(final UploadForm form) {
330         validate(form, messages -> {}, () -> uploadpage(form.dictId));
331         verifyToken(() -> uploadpage(form.dictId));
332         return protwordsService.getProtwordsFile(form.dictId).map(file -> {
333             try (InputStream inputStream = form.protwordsFile.getInputStream()) {
334                 file.update(inputStream);
335             } catch (final IOException e) {
336                 logger.warn("Failed to process a request.", e);
337                 throwValidationError(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL),
338                         () -> redirectWith(getClass(), moreUrl("uploadpage/" + form.dictId)));
339             }
340             saveInfo(messages -> messages.addSuccessUploadProtwordsFile(GLOBAL));
341             return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
342         }).orElseGet(() -> {
343             throwValidationError(messages -> messages.addErrorsFailedToUploadProtwordsFile(GLOBAL), () -> uploadpage(form.dictId));
344             return null;
345         });
346 
347     }
348 
349     // -----------------------------------------------------
350     //                                         Actually Crud
351     //                                         -------------
352     /**
353      * Create a protected word item.
354      * @param form The create form.
355      * @return The HTML response.
356      */
357     @Execute
358     @Secured({ ROLE })
359     public HtmlResponse create(final CreateForm form) {
360         verifyCrudMode(form.crudMode, CrudMode.CREATE, form.dictId);
361         validate(form, messages -> {}, this::asEditHtml);
362         verifyToken(this::asEditHtml);
363         createProtwordsItem(form, this::asEditHtml).ifPresent(entity -> {
364             protwordsService.store(form.dictId, entity);
365             saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
366         }).orElse(() -> throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), this::asEditHtml));
367         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
368     }
369 
370     /**
371      * Update a protected word item.
372      * @param form The edit form.
373      * @return The HTML response.
374      */
375     @Execute
376     @Secured({ ROLE })
377     public HtmlResponse update(final EditForm form) {
378         verifyCrudMode(form.crudMode, CrudMode.EDIT, form.dictId);
379         validate(form, messages -> {}, this::asEditHtml);
380         verifyToken(this::asEditHtml);
381         createProtwordsItem(form, this::asEditHtml).ifPresent(entity -> {
382             protwordsService.store(form.dictId, entity);
383             saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
384         })
385                 .orElse(() -> throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()),
386                         this::asEditHtml));
387         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
388     }
389 
390     /**
391      * Delete a protected word item.
392      * @param form The edit form.
393      * @return The HTML response.
394      */
395     @Execute
396     @Secured({ ROLE })
397     public HtmlResponse delete(final EditForm form) {
398         verifyCrudMode(form.crudMode, CrudMode.DETAILS, form.dictId);
399         validate(form, messages -> {}, this::asDetailsHtml);
400         verifyToken(this::asDetailsHtml);
401         protwordsService.getProtwordsItem(form.dictId, form.id).ifPresent(entity -> {
402             protwordsService.delete(form.dictId, entity);
403             saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
404         }).orElse(() -> {
405             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.getDisplayId()), this::asDetailsHtml);
406         });
407         return redirectWith(getClass(), moreUrl("list/1").params("dictId", form.dictId));
408     }
409 
410     //===================================================================================
411     //                                                                        Assist Logic
412     //                                                                        ============
413 
414     private static OptionalEntity<ProtwordsItem> getEntity(final CreateForm form) {
415         switch (form.crudMode) {
416         case CrudMode.CREATE:
417             final ProtwordsItem entity = new ProtwordsItem(0, StringUtil.EMPTY);
418             return OptionalEntity.of(entity);
419         case CrudMode.EDIT:
420             if (form instanceof EditForm) {
421                 return ComponentUtil.getComponent(ProtwordsService.class).getProtwordsItem(form.dictId, ((EditForm) form).id);
422             }
423             break;
424         default:
425             break;
426         }
427         return OptionalEntity.empty();
428     }
429 
430     /**
431      * Get the protected word item.
432      * @param form The create form.
433      * @param hook The error hook.
434      * @return The protected word item.
435      */
436     protected OptionalEntity<ProtwordsItem> createProtwordsItem(final CreateForm form, final VaErrorHook hook) {
437         try {
438             return createProtwordsItem(this, form, hook);
439         } catch (final ValidationErrorException e) {
440             saveToken();
441             throw e;
442         }
443     }
444 
445     /**
446      * Get the protected word item.
447      * @param action The action.
448      * @param form The create form.
449      * @param hook The error hook.
450      * @return The protected word item.
451      */
452     public static OptionalEntity<ProtwordsItem> createProtwordsItem(final FessBaseAction action, final CreateForm form,
453             final VaErrorHook hook) {
454         return getEntity(form).map(entity -> {
455             final String newInput = form.input;
456             validateProtwordsString(action, newInput, "input", hook);
457             entity.setNewInput(newInput);
458             return entity;
459         });
460     }
461 
462     // ===================================================================================
463     //                                                                        Small Helper
464     //                                                                        ============
465     /**
466      * Verify the CRUD mode.
467      * @param crudMode The CRUD mode.
468      * @param expectedMode The expected mode.
469      * @param dictId The dictionary ID.
470      */
471     protected void verifyCrudMode(final int crudMode, final int expectedMode, final String dictId) {
472         if (crudMode != expectedMode) {
473             throwValidationError(messages -> {
474                 messages.addErrorsCrudInvalidMode(GLOBAL, String.valueOf(expectedMode), String.valueOf(crudMode));
475             }, () -> asListHtml(dictId));
476         }
477     }
478 
479     /**
480      * Validate the protected word string.
481      * @param action The action.
482      * @param values The values.
483      * @param propertyName The property name.
484      * @param hook The error hook.
485      */
486     private static void validateProtwordsString(final FessBaseAction action, final String values, final String propertyName,
487             final VaErrorHook hook) {
488         // TODO validation
489     }
490 
491     // ===================================================================================
492     //                                                                              JSP
493     //                                                                           =========
494 
495     /**
496      * Get the HTML response for the dictionary index page.
497      * @return The HTML response.
498      */
499     protected HtmlResponse asDictIndexHtml() {
500         return redirect(AdminDictAction.class);
501     }
502 
503     private HtmlResponse asListHtml(final String dictId) {
504         return asHtml(path_AdminDictProtwords_AdminDictProtwordsJsp).renderWith(data -> {
505             RenderDataUtil.register(data, "protwordsItemItems", protwordsService.getProtwordsList(dictId, protwordsPager));
506         }).useForm(SearchForm.class, setup -> {
507             setup.setup(form -> {
508                 copyBeanToBean(protwordsPager, form, op -> op.include("id"));
509             });
510         });
511     }
512 
513     private HtmlResponse asEditHtml() {
514         return asHtml(path_AdminDictProtwords_AdminDictProtwordsEditJsp);
515     }
516 
517     private HtmlResponse asDetailsHtml() {
518         return asHtml(path_AdminDictProtwords_AdminDictProtwordsDetailsJsp);
519     }
520 
521 }