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 }