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.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 }