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.util;
17
18 import java.io.File;
19 import java.io.FilenameFilter;
20 import java.nio.file.Files;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import org.codelibs.core.lang.StringUtil;
27 import org.codelibs.fess.Constants;
28 import org.codelibs.fess.mylasta.direction.FessConfig;
29 import org.dbflute.optional.OptionalEntity;
30 import org.lastaflute.web.util.LaServletContextUtil;
31
32 import jakarta.servlet.ServletContext;
33
34 /**
35 * Utility class for accessing various resource paths and files in the Fess application.
36 * This class provides methods to retrieve paths for configuration files, templates, dictionaries,
37 * thumbnails, plugins, and other resources required by the Fess search engine.
38 * It supports both regular deployment and Docker container environments.
39 *
40 */
41 public class ResourceUtil {
42 /** Environment variable name for overriding the configuration path */
43 private static final String FESS_OVERRIDE_CONF_PATH = "FESS_OVERRIDE_CONF_PATH";
44
45 /** Environment variable name for specifying the application type */
46 private static final String FESS_APP_TYPE = "FESS_APP_TYPE";
47
48 /** Constant value representing Docker application type */
49 private static final String FESS_APP_DOCKER = "docker";
50
51 /**
52 * Protected constructor to prevent instantiation of this utility class.
53 * This class is designed to be used statically.
54 */
55 protected ResourceUtil() {
56 // nothing
57 }
58
59 /**
60 * Gets the HTTP URL for the OpenSearch (Fesen) server.
61 * First checks for a system-configured search engine address,
62 * then falls back to the URL configured in FessConfig.
63 *
64 * @return the HTTP URL for the OpenSearch server
65 */
66 public static String getFesenHttpUrl() {
67 final String url = SystemUtil.getSearchEngineHttpAddress();
68 if (url != null) {
69 return url;
70 }
71 final FessConfig fessConfig = ComponentUtil.getFessConfig();
72 return fessConfig.getFesenHttpUrl();
73 }
74
75 /**
76 * Gets the application type from the environment variable FESS_APP_TYPE.
77 * This is used to determine the deployment environment (e.g., "docker").
78 *
79 * @return the application type string, or empty string if not set
80 */
81 public static String getAppType() {
82 final String appType = System.getenv(FESS_APP_TYPE);
83 if (StringUtil.isNotBlank(appType)) {
84 return appType;
85 }
86 return StringUtil.EMPTY;
87 }
88
89 /**
90 * Gets the override configuration path from environment variable when running in Docker.
91 * This allows customization of the configuration directory location in containerized deployments.
92 *
93 * @return an OptionalEntity containing the override configuration path if set and running in Docker,
94 * or empty OptionalEntity otherwise
95 */
96 public static OptionalEntity<String> getOverrideConfPath() {
97 if (FESS_APP_DOCKER.equalsIgnoreCase(getAppType())) {
98 final String confPath = System.getenv(FESS_OVERRIDE_CONF_PATH);
99 if (StringUtil.isNotBlank(confPath)) {
100 return OptionalEntity.of(confPath);
101 }
102 }
103 return OptionalEntity.empty();
104 }
105
106 /**
107 * Gets the path to configuration files. In Docker environments, checks /opt/fess first,
108 * then falls back to system property FESS_CONF_PATH, and finally to WEB-INF/conf.
109 *
110 * @param names the path components to append to the configuration directory
111 * @return the Path object pointing to the configuration file or directory
112 */
113 public static Path getConfPath(final String... names) {
114 if (FESS_APP_DOCKER.equalsIgnoreCase(getAppType())) {
115 final Path confPath = Paths.get("/opt/fess", names);
116 if (Files.exists(confPath)) {
117 return confPath;
118 }
119 }
120 final String confPath = System.getProperty(Constants.FESS_CONF_PATH);
121 if (StringUtil.isNotBlank(confPath)) {
122 return Paths.get(confPath, names);
123 }
124 return getPath("WEB-INF/", "conf", names);
125 }
126
127 /**
128 * Gets the path to configuration files, falling back to classpath resources if not found.
129 * First attempts to find the file in the configuration directory, then searches the classpath.
130 *
131 * @param names the path components to append to the configuration directory
132 * @return the Path object pointing to the configuration file, either in conf directory or classpath
133 */
134 public static Path getConfOrClassesPath(final String... names) {
135 final Path confPath = getConfPath(names);
136 if (Files.exists(confPath)) {
137 return confPath;
138 }
139 return org.codelibs.core.io.ResourceUtil.getResourceAsFile(String.join("/", names)).toPath();
140 }
141
142 /**
143 * Gets the path to compiled classes directory.
144 *
145 * @param names the path components to append to the classes directory
146 * @return the Path object pointing to the classes directory
147 */
148 public static Path getClassesPath(final String... names) {
149 return getPath("WEB-INF/", "classes", names);
150 }
151
152 /**
153 * Gets the path to original files directory.
154 *
155 * @param names the path components to append to the orig directory
156 * @return the Path object pointing to the original files directory
157 */
158 public static Path getOrigPath(final String... names) {
159 return getPath("WEB-INF/", "orig", names);
160 }
161
162 /**
163 * Gets the path to email template files directory.
164 *
165 * @param names the path components to append to the mail template directory
166 * @return the Path object pointing to the mail template directory
167 */
168 public static Path getMailTemplatePath(final String... names) {
169 return getPath("WEB-INF/", "mail", names);
170 }
171
172 /**
173 * Gets the path to view template files directory.
174 *
175 * @param names the path components to append to the view template directory
176 * @return the Path object pointing to the view template directory
177 */
178 public static Path getViewTemplatePath(final String... names) {
179 return getPath("WEB-INF/", "view", names);
180 }
181
182 /**
183 * Gets the path to dictionary files directory.
184 *
185 * @param names the path components to append to the dictionary directory
186 * @return the Path object pointing to the dictionary directory
187 */
188 public static Path getDictionaryPath(final String... names) {
189 return getPath("WEB-INF/", "dict", names);
190 }
191
192 /**
193 * Gets the path to thumbnail files directory.
194 *
195 * @param names the path components to append to the thumbnails directory
196 * @return the Path object pointing to the thumbnails directory
197 */
198 public static Path getThumbnailPath(final String... names) {
199 return getPath("WEB-INF/", "thumbnails", names);
200 }
201
202 /**
203 * Gets the path to site-specific files directory.
204 *
205 * @param names the path components to append to the site directory
206 * @return the Path object pointing to the site directory
207 */
208 public static Path getSitePath(final String... names) {
209 return getPath("WEB-INF/", "site", names);
210 }
211
212 /**
213 * Gets the path to plugin files directory.
214 *
215 * @param names the path components to append to the plugin directory
216 * @return the Path object pointing to the plugin directory
217 */
218 public static Path getPluginPath(final String... names) {
219 return getPath("WEB-INF/", "plugin", names);
220 }
221
222 /**
223 * Gets the path to the project properties file.
224 *
225 * @return the Path object pointing to the project.properties file
226 */
227 public static Path getProjectPropertiesFile() {
228 return getPath("WEB-INF/", StringUtil.EMPTY, "project.properties");
229 }
230
231 /**
232 * Gets the path to image files directory.
233 *
234 * @param names the path components to append to the images directory
235 * @return the Path object pointing to the images directory
236 */
237 public static Path getImagePath(final String... names) {
238 return getPath(StringUtil.EMPTY, "images", names);
239 }
240
241 /**
242 * Gets the path to CSS files directory.
243 *
244 * @param names the path components to append to the CSS directory
245 * @return the Path object pointing to the CSS directory
246 */
247 public static Path getCssPath(final String... names) {
248 return getPath(StringUtil.EMPTY, "css", names);
249 }
250
251 /**
252 * Gets the path to JavaScript files directory.
253 *
254 * @param names the path components to append to the JavaScript directory
255 * @return the Path object pointing to the JavaScript directory
256 */
257 public static Path getJavaScriptPath(final String... names) {
258 return getPath(StringUtil.EMPTY, "js", names);
259 }
260
261 /**
262 * Gets the path to environment-specific files directory.
263 *
264 * @param envName the environment name (e.g., "python", "ruby")
265 * @param names the path components to append to the environment directory
266 * @return the Path object pointing to the environment-specific directory
267 */
268 public static Path getEnvPath(final String envName, final String... names) {
269 return getPath("WEB-INF/", "env/" + envName, names);
270 }
271
272 /**
273 * Gets the path by trying multiple locations in order of preference.
274 * First tries to get the real path from servlet context, then checks various
275 * fallback locations including source and target directories.
276 *
277 * @param root the root directory (e.g., "WEB-INF/")
278 * @param base the base directory under root (e.g., "conf", "classes")
279 * @param names the path components to append to the base directory
280 * @return the Path object pointing to the requested resource
281 */
282 protected static Path getPath(final String root, final String base, final String... names) {
283
284 try {
285 final ServletContext servletContext = ComponentUtil.getComponent(ServletContext.class);
286 final String webinfPath = servletContext.getRealPath("/" + root + base);
287 if (webinfPath != null && Files.exists(Paths.get(webinfPath))) {
288 return Paths.get(webinfPath, names);
289 }
290 } catch (final Throwable e) {
291 // ignore
292 }
293 final String webinfBase = root + base;
294 if (Files.exists(Paths.get(webinfBase))) {
295 return Paths.get(webinfBase, names);
296 }
297 final String srcWebInfBase = "src/main/webapps" + root + base;
298 if (Files.exists(Paths.get(srcWebInfBase))) {
299 return Paths.get(srcWebInfBase, names);
300 }
301 final String targetWebInfBase = "target/fess/" + root + base;
302 if (Files.exists(Paths.get(targetWebInfBase))) {
303 return Paths.get(targetWebInfBase, names);
304 }
305 return Paths.get(webinfBase, names);
306 }
307
308 /**
309 * Gets JAR files from the WEB-INF/lib directory that start with the specified prefix.
310 *
311 * @param namePrefix the prefix that JAR file names should start with
312 * @return an array of File objects representing matching JAR files, or empty array if none found
313 */
314 public static File[] getJarFiles(final String namePrefix) {
315 final ServletContext context = LaServletContextUtil.getServletContext();
316 if (context == null) {
317 return new File[0];
318 }
319 final String libPath = context.getRealPath("/WEB-INF/lib");
320 if (StringUtil.isBlank(libPath)) {
321 return new File[0];
322 }
323 final File libDir = new File(libPath);
324 if (!libDir.exists()) {
325 return new File[0];
326 }
327 return libDir.listFiles((file, name) -> name.startsWith(namePrefix));
328 }
329
330 /**
331 * Gets plugin JAR files from the plugin directory that start with the specified prefix.
332 *
333 * @param namePrefix the prefix that plugin JAR file names should start with
334 * @return an array of File objects representing matching plugin JAR files, or empty array if none found
335 */
336 public static File[] getPluginJarFiles(final String namePrefix) {
337 return getPluginJarFiles((file, name) -> name.startsWith(namePrefix));
338 }
339
340 /**
341 * Gets plugin JAR files from the plugin directory that match the specified filter.
342 *
343 * @param filter the FilenameFilter to apply when selecting plugin JAR files
344 * @return an array of File objects representing matching plugin JAR files, or empty array if none found
345 */
346 public static File[] getPluginJarFiles(final FilenameFilter filter) {
347 final File libDir = getPluginPath().toFile();
348 if (!libDir.exists()) {
349 return new File[0];
350 }
351 return libDir.listFiles(filter);
352 }
353
354 /**
355 * Resolves system properties in a given string.
356 * @param value The string to resolve.
357 * @return The resolved string.
358 */
359 public static String resolve(final String value) {
360 if (value == null) {
361 return null;
362 }
363
364 final StringBuffer tunedText = new StringBuffer(value.length());
365 final Pattern pattern = Pattern.compile("(\\$\\{([\\w\\.]+)\\})");
366 final Matcher matcher = pattern.matcher(value);
367 while (matcher.find()) {
368 final String key = matcher.group(2);
369 String replacement = System.getProperty(key);
370 if (replacement == null) {
371 replacement = matcher.group(1);
372 }
373 matcher.appendReplacement(tunedText, replacement.replace("\\", "\\\\").replace("$", "\\$"));
374
375 }
376 matcher.appendTail(tunedText);
377 return tunedText.toString();
378 }
379 }