1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.thumbnail.impl;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.InputStreamReader;
21 import java.nio.charset.Charset;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Timer;
25 import java.util.TimerTask;
26 import java.util.concurrent.TimeUnit;
27
28 import javax.annotation.PostConstruct;
29
30 import org.apache.logging.log4j.LogManager;
31 import org.apache.logging.log4j.Logger;
32 import org.codelibs.core.io.CloseableUtil;
33 import org.codelibs.core.io.CopyUtil;
34 import org.codelibs.core.lang.StringUtil;
35 import org.codelibs.fess.util.ComponentUtil;
36
37 public class CommandGenerator extends BaseThumbnailGenerator {
38 private static final Logger logger = LogManager.getLogger(CommandGenerator.class);
39
40 protected List<String> commandList;
41
42 protected long commandTimeout = 30 * 1000L;
43
44 protected long commandDestroyTimeout = 5 * 1000L;
45
46 protected File baseDir;
47
48 private volatile Timer destoryTimer;
49
50 @PostConstruct
51 public void init() {
52 if (logger.isDebugEnabled()) {
53 logger.debug("Initialize {}", this.getClass().getSimpleName());
54 }
55 if (baseDir == null) {
56 baseDir = new File(System.getProperty("java.io.tmpdir"));
57 }
58 destoryTimer = new Timer("CommandGeneratorDestoryTimer-" + System.currentTimeMillis(), true);
59 }
60
61 @Override
62 public void destroy() {
63 destoryTimer.cancel();
64 destoryTimer = null;
65 }
66
67 @Override
68 public boolean generate(final String thumbnailId, final File outputFile) {
69 if (logger.isDebugEnabled()) {
70 logger.debug("Generate Thumbnail: {}", thumbnailId);
71 }
72
73 if (outputFile.exists()) {
74 if (logger.isDebugEnabled()) {
75 logger.debug("The thumbnail file exists: {}", outputFile.getAbsolutePath());
76 }
77 return true;
78 }
79
80 final File parentFile = outputFile.getParentFile();
81 if (!parentFile.exists()) {
82 parentFile.mkdirs();
83 }
84 if (!parentFile.isDirectory()) {
85 logger.warn("Not found: {}", parentFile.getAbsolutePath());
86 return false;
87 }
88
89 return process(thumbnailId, responseData -> {
90 final File tempFile = ComponentUtil.getSystemHelper().createTempFile("thumbnail_", "");
91 try {
92 CopyUtil.copy(responseData.getResponseBody(), tempFile);
93
94 final String tempPath = tempFile.getAbsolutePath();
95 final String outputPath = outputFile.getAbsolutePath();
96 final List<String> cmdList = new ArrayList<>();
97 for (final String value : commandList) {
98 cmdList.add(expandPath(value.replace("${url}", tempPath).replace("${outputFile}", outputPath)));
99 }
100
101 executeCommand(thumbnailId, cmdList);
102
103 if (outputFile.isFile() && outputFile.length() == 0) {
104 logger.warn("Thumbnail File is empty. ID is {}", thumbnailId);
105 if (outputFile.delete()) {
106 logger.info("Deleted: {}", outputFile.getAbsolutePath());
107 }
108 updateThumbnailField(thumbnailId, StringUtil.EMPTY);
109 return false;
110 }
111
112 if (logger.isDebugEnabled()) {
113 logger.debug("Thumbnail File: {}", outputPath);
114 }
115 return true;
116 } catch (final Exception e) {
117 logger.warn("Failed to process ", e);
118 updateThumbnailField(thumbnailId, StringUtil.EMPTY);
119 return false;
120 } finally {
121 if (tempFile != null && !tempFile.delete()) {
122 logger.debug("Failed to delete {}", tempFile.getAbsolutePath());
123 }
124 }
125 });
126
127 }
128
129 protected void executeCommand(final String thumbnailId, final List<String> cmdList) {
130 ProcessBuilder pb = null;
131 Process p = null;
132
133 if (logger.isDebugEnabled()) {
134 logger.debug("Thumbnail Command: {}", cmdList);
135 }
136
137 TimerTask task = null;
138 try {
139 pb = new ProcessBuilder(cmdList);
140 pb.directory(baseDir);
141 pb.redirectErrorStream(true);
142
143 p = pb.start();
144
145 task = new ProcessDestroyer(p, cmdList, commandTimeout);
146 try {
147 destoryTimer.schedule(task, commandTimeout);
148
149 String line;
150 BufferedReader br = null;
151 try {
152 br = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.defaultCharset()));
153 while ((line = br.readLine()) != null) {
154 if (logger.isDebugEnabled()) {
155 logger.debug(line);
156 }
157 }
158 } finally {
159 CloseableUtil.closeQuietly(br);
160 }
161
162 p.waitFor();
163 } catch (final Exception e) {
164 p.destroy();
165 }
166 } catch (final Exception e) {
167 logger.warn("Failed to generate a thumbnail of {}", thumbnailId, e);
168 }
169 if (task != null) {
170 task.cancel();
171 task = null;
172 }
173 }
174
175 protected static class ProcessDestroyer extends TimerTask {
176
177 private final Process p;
178
179 private final List<String> commandList;
180
181 private final long timeout;
182
183 protected ProcessDestroyer(final Process p, final List<String> commandList, final long timeout) {
184 this.p = p;
185 this.commandList = commandList;
186 this.timeout = timeout;
187 }
188
189 @Override
190 public void run() {
191 logger.warn("CommandGenerator is timed out: {}", commandList);
192 try {
193 p.destroyForcibly().waitFor(timeout, TimeUnit.MILLISECONDS);
194 } catch (final Exception e) {
195 logger.warn("Failed to stop destroyer.", e);
196 }
197 }
198 }
199
200 public void setCommandList(final List<String> commandList) {
201 this.commandList = commandList;
202 }
203
204 public void setCommandTimeout(final long commandTimeout) {
205 this.commandTimeout = commandTimeout;
206 }
207
208 public void setBaseDir(final File baseDir) {
209 this.baseDir = baseDir;
210 }
211
212 public void setCommandDestroyTimeout(final long commandDestroyTimeout) {
213 this.commandDestroyTimeout = commandDestroyTimeout;
214 }
215
216 }