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