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.job;
17  
18  import static org.codelibs.core.stream.StreamUtil.stream;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.commons.lang3.RandomStringUtils;
25  import org.apache.commons.lang3.SystemUtils;
26  import org.apache.logging.log4j.LogManager;
27  import org.apache.logging.log4j.Logger;
28  import org.codelibs.core.lang.StringUtil;
29  import org.codelibs.core.timer.TimeoutTask;
30  import org.codelibs.fess.Constants;
31  import org.codelibs.fess.exception.JobProcessingException;
32  import org.codelibs.fess.exec.SuggestCreator;
33  import org.codelibs.fess.helper.ProcessHelper;
34  import org.codelibs.fess.mylasta.direction.FessConfig;
35  import org.codelibs.fess.util.ComponentUtil;
36  import org.codelibs.fess.util.InputStreamThread;
37  import org.codelibs.fess.util.JobProcess;
38  import org.codelibs.fess.util.ResourceUtil;
39  import org.codelibs.fess.util.SystemUtil;
40  
41  import jakarta.servlet.ServletContext;
42  
43  /**
44   * This job is responsible for executing the suggest creator process.
45   * It builds and runs a command-line process to generate suggest data,
46   * handling classpath setup, system properties, and process monitoring.
47   */
48  public class SuggestJob extends ExecJob {
49  
50      private static final Logger logger = LogManager.getLogger(SuggestJob.class);
51  
52      /**
53       * Constructs a new suggest job.
54       */
55      public SuggestJob() {
56          // do nothing
57      }
58  
59      @Override
60      public String execute() {
61          final StringBuilder resultBuf = new StringBuilder();
62  
63          if (sessionId == null) { // create session id
64              sessionId = RandomStringUtils.randomAlphabetic(15);
65          }
66          resultBuf.append("Session Id: ").append(sessionId).append("\n");
67          if (jobExecutor != null) {
68              jobExecutor.addShutdownListener(() -> ComponentUtil.getProcessHelper().destroyProcess(sessionId));
69          }
70  
71          final TimeoutTask timeoutTask = createTimeoutTask();
72          try {
73              executeSuggestCreator();
74          } catch (final Exception e) {
75              logger.warn("Failed to create suggest data.", e);
76              resultBuf.append(e.getMessage()).append("\n");
77          } finally {
78              if (timeoutTask != null && !timeoutTask.isCanceled()) {
79                  timeoutTask.cancel();
80              }
81          }
82  
83          return resultBuf.toString();
84  
85      }
86  
87      /**
88       * Executes the suggest creator process.
89       * This method constructs the command line arguments and starts the process.
90       * @throws JobProcessingException if the process fails.
91       */
92      protected void executeSuggestCreator() {
93          final List<String> cmdList = new ArrayList<>();
94          final String cpSeparator = SystemUtils.IS_OS_WINDOWS ? ";" : ":";
95          final ServletContext servletContext = ComponentUtil.getComponent(ServletContext.class);
96          final ProcessHelper processHelper = ComponentUtil.getProcessHelper();
97          final FessConfig fessConfig = ComponentUtil.getFessConfig();
98  
99          cmdList.add(fessConfig.getJavaCommandPath());
100 
101         // -cp
102         cmdList.add("-cp");
103         final StringBuilder buf = new StringBuilder(100);
104         ResourceUtil.getOverrideConfPath().ifPresent(p -> {
105             buf.append(p);
106             buf.append(cpSeparator);
107         });
108         final String confPath = System.getProperty(Constants.FESS_CONF_PATH);
109         if (StringUtil.isNotBlank(confPath)) {
110             buf.append(confPath);
111             buf.append(cpSeparator);
112         }
113         // WEB-INF/env/suggest/resources
114         buf.append("WEB-INF");
115         buf.append(File.separator);
116         buf.append("env");
117         buf.append(File.separator);
118         buf.append(getExecuteType());
119         buf.append(File.separator);
120         buf.append("resources");
121         buf.append(cpSeparator);
122         // WEB-INF/classes
123         buf.append("WEB-INF");
124         buf.append(File.separator);
125         buf.append("classes");
126         // target/classes
127         final String userDir = System.getProperty("user.dir");
128         final File targetDir = new File(userDir, "target");
129         final File targetClassesDir = new File(targetDir, "classes");
130         if (targetClassesDir.isDirectory()) {
131             buf.append(cpSeparator);
132             buf.append(targetClassesDir.getAbsolutePath());
133         }
134         // WEB-INF/lib
135         appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/lib")),
136                 "WEB-INF" + File.separator + "lib" + File.separator);
137         // WEB-INF/env/suggest/lib
138         appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/env/" + getExecuteType() + "/lib")),
139                 "WEB-INF" + File.separator + "env" + File.separator + getExecuteType() + File.separator + "lib" + File.separator);
140         // WEB-INF/plugin
141         appendJarFile(cpSeparator, buf, new File(servletContext.getRealPath("/WEB-INF/plugin")),
142                 "WEB-INF" + File.separator + "plugin" + File.separator);
143         final File targetLibDir = new File(targetDir, "fess" + File.separator + "WEB-INF" + File.separator + "lib");
144         if (targetLibDir.isDirectory()) {
145             appendJarFile(cpSeparator, buf, targetLibDir, targetLibDir.getAbsolutePath() + File.separator);
146         }
147         cmdList.add(buf.toString());
148 
149         if (useLocalFesen) {
150             final String httpAddress = SystemUtil.getSearchEngineHttpAddress();
151             if (StringUtil.isNotBlank(httpAddress)) {
152                 cmdList.add("-D" + Constants.FESS_SEARCH_ENGINE_HTTP_ADDRESS + "=" + httpAddress);
153             }
154         }
155 
156         final String systemLastaEnv = System.getProperty("lasta.env");
157         if (StringUtil.isNotBlank(systemLastaEnv)) {
158             if ("web".equals(systemLastaEnv)) {
159                 cmdList.add("-Dlasta.env=" + getExecuteType());
160             } else {
161                 cmdList.add("-Dlasta.env=" + systemLastaEnv);
162             }
163         } else if (StringUtil.isNotBlank(lastaEnv)) {
164             cmdList.add("-Dlasta.env=" + lastaEnv);
165         } else {
166             cmdList.add("-Dlasta.env=" + getExecuteType());
167         }
168 
169         addFessConfigProperties(cmdList);
170         addFessSystemProperties(cmdList);
171         addFessCustomSystemProperties(cmdList, fessConfig.getJobSystemPropertyFilterPattern());
172         addSystemProperty(cmdList, Constants.FESS_CONF_PATH, null, null);
173         cmdList.add("-Dfess." + getExecuteType() + ".process=true");
174         if (logFilePath == null) {
175             final String value = System.getProperty("fess.log.path");
176             logFilePath = value != null ? value : new File(targetDir, "logs").getAbsolutePath();
177         }
178         cmdList.add("-Dfess.log.path=" + logFilePath);
179         addSystemProperty(cmdList, "fess.log.name", getLogName("fess"), getLogName(StringUtil.EMPTY));
180         if (logLevel == null) {
181             addSystemProperty(cmdList, "fess.log.level", null, null);
182         } else {
183             cmdList.add("-Dfess.log.level=" + logLevel);
184         }
185         stream(fessConfig.getJvmSuggestOptionsAsArray())
186                 .of(stream -> stream.filter(StringUtil::isNotBlank).forEach(value -> cmdList.add(value)));
187 
188         File ownTmpDir = null;
189         final String tmpDir = System.getProperty("java.io.tmpdir");
190         if (fessConfig.isUseOwnTmpDir() && StringUtil.isNotBlank(tmpDir)) {
191             ownTmpDir = new File(tmpDir, "fessTmpDir_" + sessionId);
192             if (ownTmpDir.mkdirs()) {
193                 cmdList.add("-Djava.io.tmpdir=" + ownTmpDir.getAbsolutePath());
194             } else {
195                 ownTmpDir = null;
196             }
197         }
198 
199         if (!jvmOptions.isEmpty()) {
200             jvmOptions.stream().filter(StringUtil::isNotBlank).forEach(cmdList::add);
201         }
202 
203         cmdList.add(SuggestCreator.class.getCanonicalName());
204 
205         cmdList.add("--sessionId");
206         cmdList.add(sessionId);
207 
208         final File propFile = ComponentUtil.getSystemHelper().createTempFile(getExecuteType() + "_", ".properties");
209         try {
210             cmdList.add("-p");
211             cmdList.add(propFile.getAbsolutePath());
212             createSystemProperties(cmdList, propFile);
213 
214             final File baseDir = new File(servletContext.getRealPath("/WEB-INF")).getParentFile();
215 
216             if (logger.isInfoEnabled()) {
217                 logger.info("SuggestCreator: \nDirectory={}\nOptions={}", baseDir, cmdList);
218             }
219 
220             final JobProcess jobProcess = processHelper.startProcess(sessionId, cmdList, pb -> {
221                 pb.directory(baseDir);
222                 pb.redirectErrorStream(true);
223             });
224 
225             final InputStreamThread it = jobProcess.getInputStreamThread();
226             it.start();
227 
228             final Process currentProcess = jobProcess.getProcess();
229             currentProcess.waitFor();
230             it.join(5000);
231 
232             final int exitValue = currentProcess.exitValue();
233 
234             if (logger.isInfoEnabled()) {
235                 logger.info("SuggestCreator: Exit Code={} - Process Output:\n{}", exitValue, it.getOutput());
236             }
237             if (exitValue != 0) {
238                 final StringBuilder out = new StringBuilder();
239                 if (processTimeout) {
240                     out.append("Process is terminated due to ").append(timeout).append(" second exceeded.\n");
241                 }
242                 out.append("Exit Code: ").append(exitValue).append("\nOutput:\n").append(it.getOutput());
243                 throw new JobProcessingException(out.toString());
244             }
245             ComponentUtil.getPopularWordHelper().clearCache();
246         } catch (final JobProcessingException e) {
247             throw e;
248         } catch (final Exception e) {
249             throw new JobProcessingException("SuggestCreator Process terminated.", e);
250         } finally {
251             try {
252                 processHelper.destroyProcess(sessionId);
253             } finally {
254                 if (propFile != null && !propFile.delete()) {
255                     logger.warn("Failed to delete properties file: {}", propFile.getAbsolutePath());
256                 }
257                 deleteTempDir(ownTmpDir);
258             }
259         }
260     }
261 
262     @Override
263     protected String getExecuteType() {
264         return Constants.EXECUTE_TYPE_SUGGEST;
265     }
266 
267 }