KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > util > CmsRfsFileViewer


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/util/CmsRfsFileViewer.java,v $
3  * Date : $Date: 2006/11/23 16:59:38 $
4  * Version: $Revision: 1.19 $
5  *
6  * This library is part of OpenCms -
7  * the Open Source Content Mananagement System
8  *
9  * Copyright (c) 2005 Alkacon Software GmbH (http://www.alkacon.com)
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2.1 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * For further information about Alkacon Software GmbH, please see the
22  * company website: http://www.alkacon.com
23  *
24  * For further information about OpenCms, please see the
25  * project website: http://www.opencms.org
26  *
27  * You should have received a copy of the GNU Lesser General Public
28  * License along with this library; if not, write to the Free Software
29  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30  */

31
32 package org.opencms.util;
33
34 import org.opencms.i18n.CmsEncoder;
35 import org.opencms.main.CmsIllegalArgumentException;
36 import org.opencms.main.CmsLog;
37 import org.opencms.main.CmsRuntimeException;
38 import org.opencms.main.OpenCms;
39
40 import java.io.BufferedReader JavaDoc;
41 import java.io.ByteArrayOutputStream JavaDoc;
42 import java.io.File JavaDoc;
43 import java.io.FileInputStream JavaDoc;
44 import java.io.FileNotFoundException JavaDoc;
45 import java.io.IOException JavaDoc;
46 import java.io.InputStreamReader JavaDoc;
47 import java.io.LineNumberReader JavaDoc;
48 import java.io.OutputStreamWriter JavaDoc;
49 import java.nio.charset.Charset JavaDoc;
50 import java.nio.charset.IllegalCharsetNameException JavaDoc;
51 import java.nio.charset.UnsupportedCharsetException JavaDoc;
52 import java.util.HashMap JavaDoc;
53 import java.util.Map JavaDoc;
54 import java.util.Stack JavaDoc;
55
56 import org.apache.commons.logging.Log;
57
58 /**
59  * The representation of a RFS file along with the settings to provide
60  * access to certain portions (amount of lines) of it. <p>
61  *
62  * Most often the underlying file will be the OpenCms logfile. <p>
63  *
64  * The portion of the file that is shown is defined by a "window" of "windowSize" lines of text
65  * at a position "windowPosition" which is an enumeration of windows in ascending order. <p>
66  *
67  * @author Achim Westermann
68  *
69  * @version $Revision: 1.19 $
70  *
71  * @since 6.0.0
72  */

73 public class CmsRfsFileViewer implements Cloneable JavaDoc {
74
75     /** The log object for this class. */
76     protected static final Log LOG = CmsLog.getLog(CmsRfsFileViewer.class);
77
78     /** Decides whethter the view onto the underlying file via readFilePortion is enabled. */
79     private boolean m_enabled;
80
81     /** The character encoding of the underlying file. */
82     private Charset JavaDoc m_fileEncoding;
83
84     /** Maps file paths to internal info instances. */
85     protected Map JavaDoc m_fileName2lineIndex;
86
87     /** The path to the underlying file. */
88     protected String JavaDoc m_filePath;
89
90     /**
91      * If value is <code>true</code>, all setter methods will throw a
92      * <code>{@link CmsRuntimeException}</code><p>.
93      *
94      * Only the method <code>{@link #clone()}</code> returns a clone that has set this
95      * member to <code>false</code> allowing modification to take place.<p>
96      */

97     private boolean m_frozen;
98
99     /**
100      * If true the represented file is a standard OpenCms log file and may be displayed
101      * in more convenient ways (in future versions) because the format is known.
102      */

103     private boolean m_isLogfile;
104
105     /** The current window (numbered from zero to amount of possible different windows). */
106     protected int m_windowPos;
107
108     /** The amount of lines to show. */
109     protected int m_windowSize;
110
111     /**
112      * Creates an instance with default settings that tries to use the log file path obtained
113      * from <code>{@link OpenCms}'s {@link org.opencms.main.CmsSystemInfo}</code> instance.<p>
114      *
115      * If the log file path is invalid or not configured correctly a logging is performed and the
116      * path remains empty to allow user-specified file selection.<p>
117      */

118     public CmsRfsFileViewer() {
119
120         m_isLogfile = true;
121         m_fileName2lineIndex = new HashMap JavaDoc();
122         // system default charset: see http://java.sun.com/j2se/corejava/intl/reference/faqs/index.html#default-encoding
123
m_fileEncoding = Charset.forName(new OutputStreamWriter JavaDoc(new ByteArrayOutputStream JavaDoc()).getEncoding());
124         m_enabled = true;
125         m_windowSize = 200;
126
127     }
128
129     /**
130      * Internal helper that throws a <code>{@link CmsRuntimeException}</code> if the
131      * configuration of this instance has been frozen ({@link #setFrozen(boolean)}).<p>
132      *
133      * @throws CmsRuntimeException if the configuration of this instance has been frozen
134      * ({@link #setFrozen(boolean)})
135      */

136     private void checkFrozen() throws CmsRuntimeException {
137
138         if (m_frozen) {
139             throw new CmsRuntimeException(Messages.get().container(Messages.ERR_FILE_VIEW_SETTINGS_FROZEN_0));
140         }
141     }
142
143     /**
144      * Returns a clone of this file view settings that is not "frozen" and therefore allows modifications.<p>
145      *
146      * Every instance that plans to modify settings has to obtain a clone first that may be
147      * modified. The original instance returned from
148      * (<code>{@link org.opencms.workplace.CmsWorkplaceManager#getFileViewSettings()}</code>) will throw
149      * a <code>{@link CmsRuntimeException}</code> for each setter invocation. <p>
150      *
151      * @return a clone of this file view settings that is not "frozen" and therefore allows modifications
152      */

153     public Object JavaDoc clone() {
154
155         // first run after installation: filePath is null:
156
if (m_filePath == null) {
157             // below that runlevel the following call will fail (not initialized from config yet):
158
if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
159                 m_filePath = OpenCms.getSystemInfo().getLogFileRfsPath();
160             }
161         }
162         CmsRfsFileViewer clone = new CmsRfsFileViewer();
163         try {
164             // strings are immutable: no outside modification possible.
165
clone.setFilePath(m_filePath);
166         } catch (CmsRfsException e) {
167             // will never happen because m_filePath was verified in setFilePath of this instance.
168
} catch (CmsRuntimeException e) {
169             // will never happen because m_filePath was verified in setFilePath of this instance.
170
}
171         clone.m_fileEncoding = m_fileEncoding;
172         clone.m_isLogfile = m_isLogfile;
173         clone.m_enabled = m_enabled;
174         //clone.m_windowPos = m_windowPos;
175
clone.setWindowSize(m_windowSize);
176         clone.m_fileName2lineIndex = m_fileName2lineIndex;
177         // allow clone-modifications.
178
clone.m_frozen = false;
179         return clone;
180     }
181
182     /**
183      * Returns the canonical name of the character encoding of the underlying file.<p>
184      *
185      * If no special choice is fed into
186      * <code>{@link #setFileEncoding(String)}</code> before this call
187      * always the system default character encoding is returned.<p>
188      *
189      * This value may be ignored outside and will be ignored inside if the
190      * underlying does not contain textual content.<p>
191      *
192      * @return the canonical name of the character encoding of the underlying file
193      */

194     public String JavaDoc getFileEncoding() {
195
196         return m_fileEncoding.name();
197     }
198
199     /**
200      * Returns the path denoting the file that is accessed.<p>
201      *
202      * @return the path denoting the file that is accessed
203      */

204     public String JavaDoc getFilePath() {
205
206         return m_filePath;
207     }
208
209     /**
210      * Returns true if the view's internal file path points to a log file in standard OpenCms format.<p>
211      *
212      * @return true if the view's internal file path points to a log file in standard OpenCms format
213      */

214     public boolean getIsLogfile() {
215
216         // method name is bean-convention of apache.commons.beanutils (unlike eclipse's convention for booleans)
217
return m_isLogfile;
218     }
219
220     /**
221      * Returns the start position of the current display.<p>
222      *
223      * This is a count of "windows" that
224      * consist of viewable text with "windowSize" lines of text (for a non-standard log file) or
225      * log-entries (for a standard log file).<p>
226      *
227      * @return the start position of the current display
228      */

229     public int getWindowPos() {
230
231         return m_windowPos;
232     }
233
234     /**
235      * Get the amount of lines (or entries depending on wether a standard log file is shown)
236      * to display per page. <p>
237      *
238      * @return the amount of lines to display per page
239      */

240     public int getWindowSize() {
241
242         return m_windowSize;
243     }
244
245     /**
246      * Returns true if this view upon the underlying file via
247      * <code>{@link #readFilePortion()}</code> is enabled.<p>
248      *
249      *
250      * @return true if this view upon the underlying file via
251      * <code>{@link #readFilePortion()}</code> is enabled.<p>
252      */

253     public boolean isEnabled() {
254
255         return m_enabled;
256     }
257
258     /**
259      * Return the view portion of lines of text from the underlying file or an
260      * empty String if <code>{@link #isEnabled()}</code> returns <code>false</code>.<p>
261      *
262      * @return the view portion of lines of text from the underlying file or an
263      * empty String if <code>{@link #isEnabled()}</code> returns <code>false</code>
264      * @throws CmsRfsException if something goes wrong
265      */

266     public String JavaDoc readFilePortion() throws CmsRfsException {
267
268         if (m_enabled) {
269             // if we want to view the logfile we have to set the internal m_windowPos to the last window
270
// to view the end:
271
int lines = -1;
272             int startLine;
273             if (m_isLogfile) {
274                 lines = scrollToFileEnd();
275                 // for logfile mode we show the last window of window size:
276
// it could be possible that only 4 lines are in the last window
277
// (e.g.: 123 lines with windowsize 10 -> last window has 3 lines)
278
// so we ignore the window semantics and show the n last lines:
279
startLine = lines - m_windowSize;
280             } else {
281                 m_windowPos = 0;
282                 startLine = m_windowPos * m_windowSize;
283             }
284             LineNumberReader JavaDoc reader = null;
285             try {
286                 // don't make the buffer too big, just big enough for windowSize lines (estimation: avg. of 200 characters per line)
287
// to save reading too much (this optimizes to read the first windows, much later windows will be slower...)
288
reader = new LineNumberReader JavaDoc(new BufferedReader JavaDoc(new InputStreamReader JavaDoc(
289                     new FileInputStream JavaDoc(m_filePath),
290                     m_fileEncoding)), (int)m_windowSize * 200);
291                 int currentLine = 0;
292                 // skip the lines to the current window:
293
while (startLine > currentLine) {
294                     reader.readLine();
295                     currentLine++;
296                 }
297                 StringBuffer JavaDoc result = new StringBuffer JavaDoc();
298                 String JavaDoc read = reader.readLine();
299
300                 // logfile treatment is different
301
// we invert the lines: latest come first
302
if (m_isLogfile) {
303
304                     // stack is java hall of shame member... but standard
305
Stack JavaDoc inverter = new Stack JavaDoc();
306                     for (int i = m_windowSize; i > 0 && read != null; i--) {
307                         inverter.push(read);
308                         read = reader.readLine();
309                     }
310                     // pop-off:
311
while (!inverter.isEmpty()) {
312                         result.append(inverter.pop());
313                         result.append('\n');
314                     }
315
316                 } else {
317
318                     for (int i = m_windowSize; i > 0 && read != null; i--) {
319                         result.append(read);
320                         result.append('\n');
321                         read = reader.readLine();
322                     }
323                 }
324                 return CmsEncoder.escapeXml(result.toString());
325             } catch (IOException JavaDoc ioex) {
326                 CmsRfsException ex = new CmsRfsException(Messages.get().container(
327                     Messages.ERR_FILE_ARG_ACCESS_1,
328                     m_filePath), ioex);
329                 throw ex;
330             } finally {
331                 if (reader != null) {
332                     try {
333                         reader.close();
334                     } catch (IOException JavaDoc e) {
335                         LOG.error(e);
336                     }
337
338                 }
339             }
340         } else {
341             return Messages.get().getBundle().key(Messages.GUI_FILE_VIEW_NO_PREVIEW_0);
342         }
343
344     }
345
346     /**
347      * Internally sets the member <code>m_windowPos</code> to the last available
348      * window of <code>m_windowSize</code> windows to let further calls to
349      * <code>{@link #readFilePortion()}</code> display the end of the file. <p>
350      *
351      * This method is triggered when a new file is chosen
352      * (<code>{@link #setFilePath(String)}</code>) because the amount of lines changes.
353      * This method is also triggered when a different window size is chosen
354      * (<code>{@link #setWindowSize(int)}</code>) because the amount of lines to display change.
355      *
356      * @return the amount of lines in the file to view
357      */

358     private int scrollToFileEnd() {
359
360         int lines = 0;
361         if (OpenCms.getRunLevel() < OpenCms.RUNLEVEL_3_SHELL_ACCESS) {
362             // no scrolling if system not yet fully initialized
363
} else {
364             LineNumberReader JavaDoc reader = null;
365             // shift the window position to the end of the file: this is expensive but OK for ocs logfiles as they
366
// are ltd. to 2 MB
367
try {
368                 reader = new LineNumberReader JavaDoc(
369                     new BufferedReader JavaDoc(new InputStreamReader JavaDoc(new FileInputStream JavaDoc(m_filePath))));
370                 while (reader.readLine() != null) {
371                     lines++;
372                 }
373                 reader.close();
374                 // if 11.75 windows are available, we don't want to end on window nr. 10
375
int availWindows = (int)Math.ceil(lines / m_windowSize);
376                 // we start with window 0
377
m_windowPos = availWindows - 1;
378             } catch (IOException JavaDoc ioex) {
379                 LOG.error("Unable to scroll file " + m_filePath + " to end. Ensure that it exists. ");
380             } finally {
381                 if (reader != null) {
382                     try {
383                         reader.close();
384                     } catch (Throwable JavaDoc f) {
385                         LOG.info("Unable to close reader of file " + m_filePath, f);
386                     }
387                 }
388             }
389         }
390         return lines;
391     }
392
393     /**
394      * Set the boolean that decides if the view to the underlying file via
395      * <code>{@link #readFilePortion()}</code> is enabled.<p>
396      *
397      * @param preview the boolean that decides if the view to the underlying file via
398      * <code>{@link #readFilePortion()}</code> is enabled
399      */

400     public void setEnabled(boolean preview) {
401
402         m_enabled = preview;
403     }
404
405     /**
406      * Set the character encoding of the underlying file.<p>
407      *
408      * The given String has to match a valid charset name (canonical or alias)
409      * of one of the system's supported <code>{@link Charset}</code> instances
410      * (see <code>{@link Charset#forName(java.lang.String)}</code>).<p>
411      *
412      * This setting will be used for transcoding the file when portions
413      * of it are read via <code>{@link CmsRfsFileViewer#readFilePortion()}</code>
414      * to a String. This enables to correctly display files with text in various encodings
415      * in UIs.<p>
416      *
417      * @param fileEncoding the character encoding of the underlying file to set.
418      */

419     public void setFileEncoding(String JavaDoc fileEncoding) {
420
421         checkFrozen();
422         try {
423             m_fileEncoding = Charset.forName(fileEncoding);
424         } catch (IllegalCharsetNameException JavaDoc icne) {
425             throw new CmsIllegalArgumentException(Messages.get().container(
426                 Messages.ERR_CHARSET_ILLEGAL_NAME_1,
427                 fileEncoding));
428         } catch (UnsupportedCharsetException JavaDoc ucse) {
429             throw new CmsIllegalArgumentException(Messages.get().container(
430                 Messages.ERR_CHARSET_UNSUPPORTED_1,
431                 fileEncoding));
432
433         }
434
435     }
436
437     /**
438      * Set the path in the real file system that points to the file
439      * that should be displayed.<p>
440      *
441      * This method will only suceed if the file specified by the <code>path</code>
442      * argument is valid within the file system, no folder and may be read by the
443      * OpenCms process on the current platform.<p>
444      *
445      * @param path the path in the real file system that points to the file that should be displayed to set
446      *
447      * @throws CmsRuntimeException if the configuration of this instance has been frozen
448      * @throws CmsRfsException if the given path is invalid, does not point to a file or cannot be accessed
449      */

450     public void setFilePath(String JavaDoc path) throws CmsRfsException, CmsRuntimeException {
451
452         checkFrozen();
453
454         if (path != null) {
455             // leading whitespace from CmsComboWidget causes exception
456
path = path.trim();
457         }
458         if (CmsStringUtil.isEmpty(path)) {
459             throw new CmsRfsException(Messages.get().container(
460                 Messages.ERR_FILE_ARG_EMPTY_1,
461                 new Object JavaDoc[] {String.valueOf(path)}));
462         }
463         try {
464             // just for validation :
465
File JavaDoc file = new File JavaDoc(path);
466             if (file.isDirectory()) {
467                 // if wrong configuration perform self healing:
468
if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
469                     // this deletes the illegal entry and will default to the log file path
470
m_filePath = null;
471                     m_isLogfile = true;
472                 } else {
473                     throw new CmsRfsException(Messages.get().container(
474                         Messages.ERR_FILE_ARG_IS_FOLDER_1,
475                         new Object JavaDoc[] {String.valueOf(path)}));
476                 }
477             } else if (!file.isFile()) {
478                 // if wrong configuration perform self healing:
479
if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
480                     // this deletes the illegal entry and will default to the log file path
481
m_filePath = null;
482                     m_isLogfile = true;
483                 } else {
484                     throw new CmsRfsException(Messages.get().container(
485                         Messages.ERR_FILE_ARG_NOT_FOUND_1,
486                         new Object JavaDoc[] {String.valueOf(path)}));
487                 }
488
489             } else if (!file.canRead()) {
490                 // if wrong configuration perform self healing:
491
if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
492                     // this deletes the illegal entry and will default to the log file path
493
m_filePath = null;
494                     m_isLogfile = true;
495                 } else {
496                     throw new CmsRfsException(Messages.get().container(
497                         Messages.ERR_FILE_ARG_NOT_READ_1,
498                         new Object JavaDoc[] {String.valueOf(path)}));
499                 }
500             } else {
501                 m_filePath = file.getCanonicalPath();
502             }
503         } catch (FileNotFoundException JavaDoc fnfe) {
504             // if wrong configuration perform self healing:
505
if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
506                 // this deletes the illegal entry and will default to the log file path
507
m_filePath = null;
508                 m_isLogfile = true;
509             } else {
510
511                 throw new CmsRfsException(Messages.get().container(
512                     Messages.ERR_FILE_ARG_NOT_FOUND_1,
513                     new Object JavaDoc[] {String.valueOf(path)}), fnfe);
514             }
515
516         } catch (IOException JavaDoc ioex) {
517             // if wrong configuration perform self healing:
518
if (OpenCms.getRunLevel() == OpenCms.RUNLEVEL_2_INITIALIZING) {
519                 // this deletes the illegal entry and will default to the log file path
520
m_filePath = null;
521                 m_isLogfile = true;
522             } else {
523
524                 throw new CmsRfsException(Messages.get().container(
525                     Messages.ERR_FILE_ARG_ACCESS_1,
526                     new Object JavaDoc[] {String.valueOf(path)}), ioex);
527             }
528
529         }
530     }
531
532     /**
533      * Package friendly access that allows the <code>{@link org.opencms.workplace.CmsWorkplaceManager}</code>
534      * to "freeze" this instance within the system-wide assignment in it's
535      * <code>{@link org.opencms.workplace.CmsWorkplaceManager#setFileViewSettings(org.opencms.file.CmsObject, CmsRfsFileViewer)}</code> method.<p>
536      *
537      * @param frozen if true this instance will freeze and throw <code>CmsRuntimeExceptions</code> upon setter invocations
538      *
539      * @throws CmsRuntimeException if the configuration of this instance has been frozen
540      * ({@link #setFrozen(boolean)})
541      *
542      */

543     public void setFrozen(boolean frozen) throws CmsRuntimeException {
544
545         m_frozen = frozen;
546     }
547
548     /**
549      * Set if the internal file is in standard logfile format (true) or not (false).<p>
550      *
551      * If set to true the file might be
552      * treated / displayed in a more convenient format than standard files in future.
553      * Currently it is only inverted (last lines appear first) and only the last
554      * <code>windowsize</code> lines of the file are displayed.<p>
555      *
556      * Do not activate this (it is possible from the log file viewer settings in the workplace
557      * administration) if your selected file is no log file: The display will confuse you and
558      * be more expensive (imaging scrolling a 20 MB file to view the last 200 lines). <p>
559      *
560      * @param isLogfile determines if the internal file is in standard logfile format (true) or not (false)
561      *
562      * @throws CmsRuntimeException if the configuration of this instance has been frozen
563      * ({@link #setFrozen(boolean)})
564      */

565     public void setIsLogfile(boolean isLogfile) throws CmsRuntimeException {
566
567         checkFrozen();
568         m_isLogfile = isLogfile;
569     }
570
571     /**
572      * Sets the start position of the current display.<p>
573      *
574      * This is a count of "windows" that
575      * consist of viewable text with "windowSize" lines of text (for a non-standard log file) or
576      * log-entries (for a standard log file).<p>
577      *
578      * @param windowPos the start position of the current display to set
579      * @throws CmsRuntimeException if the configuration of this instance has been frozen
580      * ({@link #setFrozen(boolean)})
581      */

582     public void setWindowPos(int windowPos) throws CmsRuntimeException {
583
584         checkFrozen();
585         m_windowPos = windowPos;
586     }
587
588     /**
589      * Set the amount of lines (or entries depending on wether a standard log file is shown)
590      * to display per page.<p>
591      *
592      * @param windowSize the amount of lines to display per page
593      * @throws CmsRuntimeException if the configuration of this instance has been frozen
594      * ({@link #setFrozen(boolean)})
595      *
596      */

597     public void setWindowSize(int windowSize) throws CmsRuntimeException {
598
599         checkFrozen();
600         m_windowSize = windowSize;
601     }
602 }
Popular Tags