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.base;
17  
18  import java.util.Map;
19  import java.util.function.Consumer;
20  
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  import org.codelibs.core.beans.util.BeanUtil;
24  import org.codelibs.core.beans.util.CopyOptions;
25  import org.codelibs.fess.Constants;
26  import org.codelibs.fess.app.web.base.login.FessLoginAssist;
27  import org.codelibs.fess.helper.AccessTokenHelper;
28  import org.codelibs.fess.helper.ActivityHelper;
29  import org.codelibs.fess.helper.SystemHelper;
30  import org.codelibs.fess.helper.ViewHelper;
31  import org.codelibs.fess.mylasta.action.FessHtmlPath;
32  import org.codelibs.fess.mylasta.action.FessMessages;
33  import org.codelibs.fess.mylasta.action.FessUserBean;
34  import org.codelibs.fess.mylasta.direction.FessConfig;
35  import org.dbflute.hook.AccessContext;
36  import org.dbflute.optional.OptionalThing;
37  import org.lastaflute.core.message.MessageManager;
38  import org.lastaflute.core.time.TimeManager;
39  import org.lastaflute.db.dbflute.accesscontext.AccessContextArranger;
40  import org.lastaflute.web.TypicalAction;
41  import org.lastaflute.web.response.ActionResponse;
42  import org.lastaflute.web.ruts.process.ActionRuntime;
43  import org.lastaflute.web.servlet.request.RequestManager;
44  import org.lastaflute.web.servlet.request.ResponseManager;
45  import org.lastaflute.web.servlet.session.SessionManager;
46  import org.lastaflute.web.validation.ActionValidator;
47  import org.lastaflute.web.validation.LaValidatable;
48  import org.lastaflute.web.validation.VaMessenger;
49  
50  import jakarta.annotation.Resource;
51  
52  /**
53   * The base action class for Fess web application.
54   * This abstract class provides common functionality for all Fess web actions,
55   * including user authentication, validation, message handling, and access context management.
56   * It extends LastaFlute's TypicalAction and implements validation and HTML path interfaces.
57   *
58   * @since 1.0
59   */
60  public abstract class FessBaseAction extends TypicalAction // has several interfaces for direct use
61          implements LaValidatable<FessMessages>, FessHtmlPath {
62  
63      /**
64       * Default constructor.
65       */
66      public FessBaseAction() {
67          super();
68      }
69  
70      // ===================================================================================
71      //                                                                          Definition
72      //                                                                          ==========
73      /** Logger instance for this class. */
74      private static final Logger logger = LogManager.getLogger(FessBaseAction.class);
75  
76      /** The application type for FESs, e.g. used by access context. */
77      protected static final String APP_TYPE = "FES"; // #change_it_first
78  
79      /** The user type for Admin, e.g. used by access context. */
80      protected static final String USER_TYPE = "A";
81  
82      // ===================================================================================
83      //                                                                           Attribute
84      //                                                                           =========
85      /** Login assistance helper for managing user authentication and session. */
86      @Resource
87      protected FessLoginAssist fessLoginAssist;
88  
89      /** Session manager for handling HTTP session operations. */
90      @Resource
91      protected SessionManager sessionManager;
92  
93      /** Configuration manager for Fess application settings. */
94      @Resource
95      protected FessConfig fessConfig;
96  
97      /** Helper for managing user activity logging and tracking. */
98      @Resource
99      protected ActivityHelper activityHelper;
100 
101     /** Manager for handling HTTP response operations. */
102     @Resource
103     protected ResponseManager responseManager;
104 
105     /** Time manager for handling date and time operations. */
106     @Resource
107     protected TimeManager timeManager;
108 
109     /** System helper for various system-level operations. */
110     @Resource
111     protected SystemHelper systemHelper;
112 
113     /** Helper for managing access tokens and API authentication. */
114     @Resource
115     protected AccessTokenHelper accessTokenHelper;
116 
117     /** Helper for view-related operations and rendering. */
118     @Resource
119     protected ViewHelper viewHelper;
120 
121     /** Manager for handling application messages and internationalization. */
122     @Resource
123     private MessageManager messageManager;
124 
125     /** Manager for handling HTTP request operations. */
126     @Resource
127     private RequestManager requestManager;
128 
129     // ===================================================================================
130     //                                                                               Hook
131     //                                                                              ======
132     // to suppress unexpected override by sub-class
133     // you should remove the 'final' if you need to override this
134     /**
135      * Hook method called before action execution.
136      * This method refreshes the user information if a user is logged in
137      * and delegates to the view helper's action hook.
138      *
139      * @param runtime the action runtime context
140      * @return the action response, or null to continue with normal processing
141      */
142     @Override
143     public ActionResponse godHandPrologue(final ActionRuntime runtime) {
144         fessLoginAssist.getSavedUserBean().ifPresent(u -> {
145             final boolean result = u.getFessUser().refresh();
146             if (logger.isDebugEnabled()) {
147                 logger.debug("Refreshed user info: result={}", result);
148             }
149         });
150         return viewHelper.getActionHook().godHandPrologue(runtime, super::godHandPrologue);
151     }
152 
153     /**
154      * Hook method called during action execution.
155      * This method delegates to the view helper's action hook for processing.
156      *
157      * @param runtime the action runtime context
158      * @return the action response, or null to continue with normal processing
159      */
160     @Override
161     public final ActionResponse godHandMonologue(final ActionRuntime runtime) {
162         return viewHelper.getActionHook().godHandMonologue(runtime, super::godHandMonologue);
163     }
164 
165     /**
166      * Hook method called after action execution.
167      * This method delegates to the view helper's action hook for cleanup.
168      *
169      * @param runtime the action runtime context
170      */
171     @Override
172     public final void godHandEpilogue(final ActionRuntime runtime) {
173         viewHelper.getActionHook().godHandEpilogue(runtime, super::godHandEpilogue);
174     }
175 
176     // #app_customize you can customize the action hook
177     /**
178      * Hook method called before action processing.
179      * This method can be overridden by subclasses to customize behavior.
180      *
181      * @param runtime the action runtime context
182      * @return the action response, or null to continue with normal processing
183      */
184     @Override
185     public ActionResponse hookBefore(final ActionRuntime runtime) { // application may override
186         return viewHelper.getActionHook().hookBefore(runtime, super::hookBefore);
187     }
188 
189     /**
190      * Hook method called in the finally block of action processing.
191      * This method delegates to the view helper's action hook for final cleanup.
192      *
193      * @param runtime the action runtime context
194      */
195     @Override
196     public void hookFinally(final ActionRuntime runtime) {
197         viewHelper.getActionHook().hookFinally(runtime, super::hookFinally);
198     }
199 
200     // ===================================================================================
201     //                                                                      Access Context
202     //                                                                      ==============
203     /**
204      * Creates a new access context arranger for database operations.
205      * This method provides a dummy implementation as Fess does not use DBFlute extensively.
206      *
207      * @return a new access context arranger
208      */
209     @Override
210     protected AccessContextArranger newAccessContextArranger() { // for framework
211         // fess does not use DBFlute, and this is unneeded so dummy
212         return resource -> {
213             final AccessContext context = new AccessContext();
214             context.setAccessLocalDateTimeProvider(() -> timeManager.currentDateTime());
215             context.setAccessUserProvider(() -> "unused");
216             return context;
217         };
218     }
219 
220     // ===================================================================================
221     //                                                                           User Info
222     //                                                                           =========
223     /**
224      * Gets the current user bean from the session.
225      * This method returns the concrete FessUserBean class instead of the generic type.
226      *
227      * @return an optional containing the current user bean, or empty if not logged in
228      */
229     @Override
230     protected OptionalThing<FessUserBean> getUserBean() { // to return as concrete class
231         return fessLoginAssist.getSavedUserBean();
232     }
233 
234     /**
235      * Returns the application type identifier for this Fess application.
236      *
237      * @return the application type string "FES"
238      */
239     @Override
240     protected String myAppType() { // for framework
241         return APP_TYPE;
242     }
243 
244     /**
245      * Returns the user type identifier for this application.
246      *
247      * @return an optional containing the user type string "A" for Admin
248      */
249     @Override
250     protected OptionalThing<String> myUserType() { // for framework
251         return OptionalThing.of(USER_TYPE); // same reason as getUserBean()
252     }
253 
254     // ===================================================================================
255     //                                                                          Validation
256     //                                                                          ==========
257     @SuppressWarnings("unchecked")
258     /**
259      * Creates a validator instance for form validation.
260      * This method uses the system helper to create a validator with Fess-specific messages.
261      *
262      * @return a new action validator instance
263      */
264     @Override
265     public ActionValidator<FessMessages> createValidator() {
266         return systemHelper.createValidator(requestManager, this::createMessages, myValidationGroups());
267     }
268 
269     /**
270      * Creates a new messages instance for handling validation and user messages.
271      * This method can be called by the application to create message containers.
272      *
273      * @return a new FessMessages instance
274      */
275     @Override
276     public FessMessages createMessages() { // application may call
277         return new FessMessages(); // overriding to change return type to concrete-class
278     }
279 
280     // ===================================================================================
281     //                                                                        Small Helper
282     //                                                                        ============
283 
284     /**
285      * Saves informational messages to the session.
286      * The messages will be displayed to the user on the next page load.
287      *
288      * @param validationMessagesLambda a lambda function to configure the messages
289      */
290     protected void saveInfo(final VaMessenger<FessMessages> validationMessagesLambda) {
291         final FessMessages messages = createMessages();
292         validationMessagesLambda.message(messages);
293         sessionManager.info().saveMessages(messages);
294     }
295 
296     /**
297      * Saves error messages to the session.
298      * The messages will be displayed to the user on the next page load.
299      *
300      * @param validationMessagesLambda a lambda function to configure the error messages
301      */
302     protected void saveError(final VaMessenger<FessMessages> validationMessagesLambda) {
303         final FessMessages messages = createMessages();
304         validationMessagesLambda.message(messages);
305         sessionManager.errors().saveMessages(messages);
306     }
307 
308     /**
309      * Copies properties from source bean to destination bean.
310      * This is a utility method that wraps BeanUtil.copyBeanToBean with custom options.
311      *
312      * @param src the source bean object
313      * @param dest the destination bean object
314      * @param option a consumer function to configure copy options
315      */
316     protected static void copyBeanToBean(final Object src, final Object dest, final Consumer<CopyOptions> option) {
317         BeanUtil.copyBeanToBean(src, dest, option);
318     }
319 
320     /**
321      * Copies properties from a map to a bean object.
322      * This is a utility method that wraps BeanUtil.copyMapToBean with custom options.
323      *
324      * @param src the source map containing property values
325      * @param dest the destination bean object
326      * @param option a consumer function to configure copy options
327      */
328     protected static void copyMapToBean(final Map<String, ? extends Object> src, final Object dest, final Consumer<CopyOptions> option) {
329         BeanUtil.copyMapToBean(src, dest, option);
330     }
331 
332     /**
333      * Copies properties from source bean to a new instance of the destination class.
334      * This is a utility method that wraps BeanUtil.copyBeanToNewBean.
335      *
336      * @param <T> the type of the destination class
337      * @param src the source bean object
338      * @param destClass the class of the destination bean
339      * @return a new instance of the destination class with copied properties
340      */
341     protected static <T> T copyBeanToNewBean(final Object src, final Class<T> destClass) {
342         return BeanUtil.copyBeanToNewBean(src, destClass);
343     }
344 
345     /**
346      * Builds a comprehensive error message from a throwable and its causes.
347      * This method traverses the cause chain and concatenates all error messages.
348      *
349      * @param t the throwable to build message from
350      * @return a string containing all error messages in the cause chain
351      */
352     protected String buildThrowableMessage(final Throwable t) {
353         final StringBuilder buf = new StringBuilder(100);
354         Throwable current = t;
355         while (current != null) {
356             buf.append(current.getLocalizedMessage()).append(' ');
357             current = current.getCause();
358         }
359         return buf.toString();
360     }
361 
362     /**
363      * Checks if a checkbox value represents an enabled state.
364      * This method considers "on" and "true" (case-insensitive) as enabled values.
365      *
366      * @param value the checkbox value to check
367      * @return true if the value represents an enabled checkbox, false otherwise
368      */
369     public static boolean isCheckboxEnabled(final String value) {
370         if (value == null) {
371             return false;
372         }
373         return Constants.ON.equalsIgnoreCase(value) || Constants.TRUE.equalsIgnoreCase(value);
374     }
375 }