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.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;// 30sec
43  
44      protected long commandDestroyTimeout = 5 * 1000L;// 5sec
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 }