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