View Javadoc
1   /**
2    * This file Copyright (c) 2003-2014 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.admininterface.pages;
35  
36  import info.magnolia.cms.core.Path;
37  import info.magnolia.init.MagnoliaConfigurationProperties;
38  import info.magnolia.module.admininterface.TemplatedMVCHandler;
39  import info.magnolia.objectfactory.Components;
40  
41  import java.io.File;
42  import java.io.FileInputStream;
43  import java.io.FileNotFoundException;
44  import java.io.FileReader;
45  import java.io.IOException;
46  import java.io.LineNumberReader;
47  import java.io.PrintWriter;
48  import java.io.StringWriter;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.Iterator;
52  
53  import javax.servlet.ServletOutputStream;
54  import javax.servlet.http.HttpServletRequest;
55  import javax.servlet.http.HttpServletResponse;
56  
57  import org.apache.commons.io.FileUtils;
58  import org.apache.commons.io.IOUtils;
59  import org.apache.commons.io.filefilter.TrueFileFilter;
60  import org.apache.commons.io.filefilter.WildcardFileFilter;
61  import org.apache.commons.lang.StringEscapeUtils;
62  import org.apache.commons.lang.StringUtils;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  public class LogViewerPage extends TemplatedMVCHandler {
67      private static final Logger log = LoggerFactory.getLogger(LogViewerPage.class);
68  
69      public static final String LOGS_FOLDER_PROPERTY = "magnolia.logs.dir";
70  
71      public LogViewerPage(String name, HttpServletRequest request, HttpServletResponse response) {
72          super(name, request, response);
73  
74          // initialize the folder variable
75          MagnoliaConfigurationProperties properties = Components.getComponent(MagnoliaConfigurationProperties.class);
76          String temp = properties.getProperty(LOGS_FOLDER_PROPERTY);
77          if (temp != null) {
78              logsFolder = Path.getAbsoluteFileSystemPath(temp);
79          }
80      }
81  
82      private String logsFolder = "";
83  
84      private String fileName = "";
85  
86      private String text = "";
87  
88      private Collection namesList = null;
89  
90      private long maxNumLinesPerPage = 50;
91  
92      private long pageNumber = 0;
93  
94      private long totalPages = 0;
95  
96      private long currentPosition = 0;
97  
98      private long fileSizeInLines = 0;
99  
100     /**
101      * the content of the select
102      *
103      * @return
104      */
105     public Collection getLogFiles() {
106 
107         ArrayList urls = new ArrayList();
108 
109         File logDir = new File(this.logsFolder);
110         Collection files = null;
111         if (logDir.exists()) {
112             files = FileUtils.listFiles(logDir, new WildcardFileFilter("*.log*"), TrueFileFilter.TRUE);
113 
114             Iterator filesIterator = files.iterator();
115             String name = "";
116             while (filesIterator.hasNext()) {
117                 name = ((File) filesIterator.next()).getName();
118                 urls.add(name);
119             }
120         }
121         return urls;
122     }
123 
124     public String refresh() {
125         displayFileContent();
126         return VIEW_SHOW;
127     }
128 
129     public String next() {
130         this.currentPosition = Math.min(this.currentPosition + this.maxNumLinesPerPage, this.fileSizeInLines - 1);
131         displayFileContent();
132         return VIEW_SHOW;
133     }
134 
135     public String previous() {
136         this.currentPosition = Math.max(0, this.currentPosition - this.maxNumLinesPerPage);
137         displayFileContent();
138         return VIEW_SHOW;
139     }
140 
141     public String begin() {
142         this.currentPosition = 0;
143         displayFileContent();
144         return VIEW_SHOW;
145     }
146 
147     public String end() {
148         if (this.fileSizeInLines > this.maxNumLinesPerPage) {
149             this.currentPosition = this.fileSizeInLines - (this.fileSizeInLines % this.maxNumLinesPerPage);
150         } else {
151             currentPosition = 0;
152         }
153         displayFileContent();
154         return VIEW_SHOW;
155     }
156 
157     public String download() throws FileNotFoundException {
158         // set mime/type
159         if (isValidPath()) {
160             File file = getFile();
161             this.getResponse().setContentType("html/text");
162             this.getResponse().setHeader("Content-Disposition", "attachment; filename=" + fileName);
163 
164             FileInputStream is = new FileInputStream(file);
165             this.getResponse().setContentLength((int) file.length());
166 
167             try {
168                 sendUnCompressed(is, this.getResponse());
169             } catch (Exception e) {
170                 log.info("File download failed [{}]: {}", fileName, e.getMessage());
171             } finally {
172                 IOUtils.closeQuietly(is);
173             }
174         }
175         return "";
176     }
177 
178     private void sendUnCompressed(java.io.InputStream is, HttpServletResponse res) throws Exception {
179         ServletOutputStream os = res.getOutputStream();
180         byte[] buffer = new byte[8192];
181         int read = 0;
182         while ((read = is.read(buffer)) > 0) {
183             os.write(buffer, 0, read);
184         }
185         os.flush();
186         os.close();
187     }
188 
189     public String displayFileContent() {
190 
191         StringWriter str = new StringWriter();
192         if (StringUtils.isNotEmpty(this.fileName) && isValidPath()) {
193             FileReader logFile = null;
194             LineNumberReader input = null;
195             try {
196                 // need file to get size
197                 File file = getFile();
198 
199                 logFile = new FileReader(file);
200                 this.fileSizeInLines = countLines(file);
201                 PrintWriter writer = new PrintWriter(str);
202 
203                 input = new LineNumberReader(logFile);
204 
205                 String line;
206                 int numLines = 0;
207                 while ((line = input.readLine()) != null) {
208 
209                     if (input.getLineNumber() >= this.currentPosition) {
210                         writer.write(StringEscapeUtils.escapeHtml(line));
211                         writer.write("<br/>");
212                         numLines++;
213                     }
214                     if (numLines >= this.maxNumLinesPerPage) {
215                         break;
216                     }
217                 }
218                 writer.flush();
219                 this.text = str.toString();
220                 writer.close();
221 
222                 setFieldValues();
223 
224             } catch (Exception e) {
225                 this.text = e.getMessage();
226                 log.error("Error can't read file:", e);
227                 return VIEW_SHOW;
228             } finally {
229 
230                 closeFile(logFile, input);
231             }
232         }
233 
234         return VIEW_SHOW;
235     }
236 
237     /**
238      * Check if given file is under logs directory.
239      */
240     private boolean isValidPath() {
241         String canonicalPath = null;
242         try {
243             File file = getFile();
244             if (file != null) {
245                 canonicalPath = file.getCanonicalPath();
246             } else {
247                 this.text = "Invalid file path.";
248                 return false;
249             }
250         } catch (IOException e) {
251             this.text = e.getMessage();
252             log.error("Can't read file: ", e);
253             return false;
254         }
255         String logFolderPath = null;
256         try {
257             logFolderPath = new File(this.logsFolder).getCanonicalPath();
258         } catch (IOException e) {
259             log.warn("Cannot resolve symlinks for logs folder '{}'. If the property '" + LOGS_FOLDER_PROPERTY +
260                     "' contains symlinks, they won't be resolved.", this.logsFolder, e);
261             logFolderPath = this.logsFolder;
262         }
263         if (StringUtils.startsWith(canonicalPath, logFolderPath)) {
264             return true;
265         }
266         this.text = "Invalid file path.";
267         return false;
268     }
269 
270     private File getFile() {
271         if (this.fileName.length() > 0) {
272             File file = new File(this.logsFolder + "/" + this.fileName);
273             return file;
274         }
275         return null;
276     }
277 
278     private void setFieldValues() {
279         this.pageNumber = (this.currentPosition / this.maxNumLinesPerPage) + 1;
280         this.totalPages = (this.fileSizeInLines / this.maxNumLinesPerPage) + 1;
281         long mod = this.currentPosition % this.maxNumLinesPerPage;
282         if (mod > 0) {
283             // someone switched page size in between the exact multiples of the pages or there is only one page in total
284             this.pageNumber++;
285             this.totalPages++;
286         }
287     }
288 
289     /* gets the number of lines for pagination */
290     private long countLines(File file) {
291         int count = 0;
292         FileReader fileReader = null;
293         LineNumberReader lineReader = null;
294         try {
295             fileReader = new FileReader(file);
296             lineReader = new LineNumberReader(fileReader);
297 
298             lineReader.skip(file.length() );
299             count = lineReader.getLineNumber() + 1;
300 
301         } catch (Exception e) {
302             count = 0;
303         } finally {
304 
305             closeFile(fileReader, lineReader);
306         }
307 
308         return count;
309     }
310 
311     private void closeFile(FileReader fileReader, LineNumberReader lineReader) {
312         try {
313             if (fileReader != null) {
314                 fileReader.close();
315 
316             }
317             if (lineReader != null) {
318                 lineReader.close();
319             }
320         } catch (Exception e) {
321 
322         }
323     }
324 
325     public String getFileName() {
326         return this.fileName;
327     }
328 
329     public void setFileName(String fileName) {
330         this.fileName = fileName;
331     }
332 
333     public Collection getNamesList() {
334         if (this.namesList == null) {
335             this.namesList = getLogFiles();
336         }
337         return this.namesList;
338     }
339 
340     public String getText() {
341         return this.text;
342     }
343 
344     public long getCurrentPosition() {
345         return this.currentPosition;
346     }
347 
348     public void setCurrentPosition(long currentPosition) {
349         // make sure number never exceeds bounds
350         this.currentPosition = Math.max(0, Math.min(currentPosition, this.fileSizeInLines - (this.fileSizeInLines % this.maxNumLinesPerPage)));
351     }
352 
353     public long getFileSizeInLines() {
354         return this.fileSizeInLines;
355     }
356 
357     public void setFileSizeInLines(long fileSizeInLines) {
358         this.fileSizeInLines = fileSizeInLines;
359     }
360 
361     public long getPageNumber() {
362         return this.pageNumber;
363     }
364 
365     public long getTotalPages() {
366         return this.totalPages;
367     }
368 
369     public long getMaxNumLinesPerPage() {
370         return this.maxNumLinesPerPage;
371     }
372 
373     /**
374      * Sets new maximum value in the range <1, LONG_MAX>. 0 or negative values are ignored.
375      * @param maxNumLinesPerPage
376      */
377     public void setMaxNumLinesPerPage(long maxNumLinesPerPage) {
378         if (maxNumLinesPerPage < 1) {
379             return;
380         }
381         this.maxNumLinesPerPage = maxNumLinesPerPage;
382     }
383 
384 }