View Javadoc
1   /*
2    * Copyright 2012-2021 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 javax.servlet.ServletContext;
25  
26  import org.apache.commons.lang3.RandomStringUtils;
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.codelibs.core.lang.StringUtil;
30  import org.codelibs.core.timer.TimeoutTask;
31  import org.codelibs.fess.Constants;
32  import org.codelibs.fess.exception.JobProcessingException;
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  
39  public class PythonJob extends ExecJob {
40      static final Logger logger = LogManager.getLogger(PythonJob.class);
41  
42      protected String filename;
43  
44      protected List<String> argList = new ArrayList<>();
45  
46      public PythonJob filename(final String filename) {
47          this.filename = filename;
48          return this;
49      }
50  
51      public PythonJob arg(final String value) {
52          argList.add(value);
53          return this;
54      }
55  
56      public PythonJob args(final String... values) {
57          stream(values).of(stream -> stream.forEach(argList::add));
58          return this;
59      }
60  
61      @Override
62      public String execute() {
63          final StringBuilder resultBuf = new StringBuilder();
64  
65          if (sessionId == null) { // create session id
66              sessionId = RandomStringUtils.randomAlphabetic(15);
67          }
68          resultBuf.append("Session Id: ").append(sessionId).append("\n");
69          if (jobExecutor != null) {
70              jobExecutor.addShutdownListener(() -> ComponentUtil.getProcessHelper().destroyProcess(sessionId));
71          }
72  
73          final TimeoutTask timeoutTask = createTimeoutTask();
74          try {
75              executePython();
76          } catch (final Exception e) {
77              logger.warn("Failed to run python command.", e);
78              resultBuf.append(e.getMessage()).append("\n");
79          } finally {
80              if (timeoutTask != null && !timeoutTask.isCanceled()) {
81                  timeoutTask.cancel();
82              }
83          }
84  
85          return resultBuf.toString();
86  
87      }
88  
89      protected void executePython() {
90          final List<String> cmdList = new ArrayList<>();
91          final ServletContext servletContext = ComponentUtil.getComponent(ServletContext.class);
92          final ProcessHelper processHelper = ComponentUtil.getProcessHelper();
93          final FessConfig fessConfig = ComponentUtil.getFessConfig();
94  
95          if (StringUtil.isBlank(filename)) {
96              throw new JobProcessingException("Python script is not specified.");
97          }
98  
99          cmdList.add(fessConfig.getPythonCommandPath());
100 
101         cmdList.add(getPyFilePath());
102 
103         cmdList.addAll(argList);
104 
105         try {
106 
107             final File baseDir = new File(servletContext.getRealPath("/WEB-INF")).getParentFile();
108 
109             if (logger.isInfoEnabled()) {
110                 logger.info("Python: \nDirectory={}\nOptions={}", baseDir, cmdList);
111             }
112 
113             final JobProcess jobProcess = processHelper.startProcess(sessionId, cmdList, pb -> {
114                 pb.directory(baseDir);
115                 pb.redirectErrorStream(true);
116             });
117 
118             final InputStreamThread it = jobProcess.getInputStreamThread();
119             it.start();
120 
121             final Process currentProcess = jobProcess.getProcess();
122             currentProcess.waitFor();
123             it.join(5000);
124 
125             final int exitValue = currentProcess.exitValue();
126 
127             if (logger.isInfoEnabled()) {
128                 logger.info("Python: Exit Code={} - Process Output:\n{}", exitValue, it.getOutput());
129             }
130             if (exitValue != 0) {
131                 final StringBuilder out = new StringBuilder();
132                 if (processTimeout) {
133                     out.append("Process is terminated due to ").append(timeout).append(" second exceeded.\n");
134                 }
135                 out.append("Exit Code: ").append(exitValue).append("\nOutput:\n").append(it.getOutput());
136                 throw new JobProcessingException(out.toString());
137             }
138             ComponentUtil.getPopularWordHelper().clearCache();
139         } catch (final JobProcessingException e) {
140             throw e;
141         } catch (final Exception e) {
142             throw new JobProcessingException("Python Process terminated.", e);
143         } finally {
144             processHelper.destroyProcess(sessionId);
145 
146         }
147     }
148 
149     protected String getPyFilePath() {
150         final StringBuilder buf = new StringBuilder(100);
151         buf.append("WEB-INF");
152         buf.append(File.separator);
153         buf.append("env");
154         buf.append(File.separator);
155         buf.append(getExecuteType());
156         buf.append(File.separator);
157         buf.append("resources");
158         buf.append(File.separator);
159         buf.append(filename.replaceAll("\\.\\.+", ""));
160         return buf.toString();
161     }
162 
163     @Override
164     protected String getExecuteType() {
165         return Constants.EXECUTE_TYPE_PYTHON;
166     }
167 }