001package net.sf.logdistiller.gui;
002
003/*
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017import java.awt.Font;
018import java.awt.LayoutManager;
019import java.awt.event.ActionEvent;
020import java.awt.event.KeyEvent;
021import java.io.*;
022import java.lang.reflect.*;
023import java.text.MessageFormat;
024import java.text.ParseException;
025import java.util.*;
026import javax.swing.*;
027import javax.xml.parsers.ParserConfigurationException;
028
029import org.xml.sax.ErrorHandler;
030import org.xml.sax.SAXException;
031import org.xml.sax.SAXParseException;
032
033import com.jgoodies.forms.builder.DefaultFormBuilder;
034import com.jgoodies.forms.factories.ButtonBarFactory;
035import com.jgoodies.forms.factories.DefaultComponentFactory;
036import com.jgoodies.forms.layout.FormLayout;
037import com.sun.syndication.io.XmlReader;
038
039import org.apache.commons.io.IOUtils;
040
041import net.sf.logdistiller.LogDistillation;
042import net.sf.logdistiller.LogDistiller;
043import net.sf.logdistiller.LogEvent;
044import net.sf.logdistiller.LogType;
045import net.sf.logdistiller.LogTypes;
046import net.sf.logdistiller.reports.TextReport;
047import net.sf.logdistiller.util.FormatUtil;
048import net.sf.logdistiller.util.PropertiesReplacer;
049import net.sf.logdistiller.xml.DOMConfigurator;
050import net.sf.logdistiller.xml.LogDistillerEntityResolver;
051
052/**
053 * Main gui panel.
054 */
055public class MainPanel
056    implements Runnable
057{
058    private static final String TITLE = "LogDistiller " + LogDistiller.getVersion() + " interactive console";
059
060    private static final String ABOUT = TITLE + "\n\nCopyright (C) 2004-2009 Hervé Boutemy. All rights reserved.";
061
062    private final JFrame frame;
063
064    public MainPanel( JFrame frame )
065    {
066        this.frame = frame;
067        frame.setTitle( TITLE );
068    }
069
070    private Thread reloadThread;
071
072    protected JPanel panel = new JPanel();
073
074    private JTextField tfRules = new JTextField( "open or create a rules configuration file..." );
075
076    private JTextField tfSourceFile = new JTextField();
077
078    private JTextField tfSourceStats = new JTextField();
079
080    private JTextField tfOutputDir = new JTextField();
081
082    private JButton btRun = new JButton( "run" );
083
084    private JButton btBatch = new JButton( "batch" );
085
086    private JButton btSourceFile = new JButton( "..." );
087
088    private JButton btOutputDir = new JButton( "..." );
089
090    private JTextArea taResult = new JTextArea();
091
092    private LongProgressBar progress = new LongProgressBar();
093
094    private void initComponents()
095    {
096        tfSourceFile.setEditable( false );
097        btSourceFile.addActionListener( SwingAdapter.getActionListener( this, "btSourceFileActionPerformed" ) );
098        tfSourceStats.setEditable( false );
099        tfOutputDir.setEditable( false );
100        btOutputDir.addActionListener( SwingAdapter.getActionListener( this, "btOutputDirActionPerformed" ) );
101        tfRules.setEditable( false );
102        taResult.setEditable( false );
103        btRun.addActionListener( SwingAdapter.getActionListener( this, "btRunActionPerformed" ) );
104        btRun.setEnabled( false );
105        btBatch.addActionListener( SwingAdapter.getActionListener( this, "btBatchActionPerformed" ) );
106        btBatch.setEnabled( false );
107
108        taResult.setFont( new Font( "Monospaced", Font.PLAIN, taResult.getFont().getSize() ) );
109
110        progress.setStringPainted( true );
111        progress.setFont( new Font( "Monospaced", Font.PLAIN, progress.getFont().getSize() ) );
112    }
113
114    private void addLabel( String label, String id )
115    {
116        panel.add( new JLabel( label ), id );
117    }
118
119    private void addSeparator( String label, String id )
120    {
121        panel.add( DefaultComponentFactory.getInstance().createSeparator( label ), id );
122    }
123
124    public final static SimpleLayoutConstraintsManager SIMPLE_LAYOUT_CONSTRAINTS_MANAGER =
125        SimpleLayoutConstraintsManager.getLayoutConstraintsManager( MainPanel.class.getResourceAsStream( "gui.xml" ) );
126
127    public LayoutManager createLayout( String layoutId )
128    {
129        return MainPanel.SIMPLE_LAYOUT_CONSTRAINTS_MANAGER.getLayout( layoutId );
130    }
131
132    public JComponent buildPanel()
133    {
134        initComponents();
135        buildMenuBar();
136
137        panel.setBorder( com.jgoodies.forms.factories.Borders.DIALOG_BORDER );
138        panel.setLayout( createLayout( "mainPanel" ) );
139
140        /*
141         * org.mlc.swing.layout.LayoutConstraintsManager layoutConstraintsManager =
142         * org.mlc.swing.layout.LayoutConstraintsManager
143         * .getLayoutConstraintsManager(MainPanel.class.getResourceAsStream("gui.xml"));
144         * panel.setLayout(layoutConstraintsManager.createLayout("mainPanel", panel)); new
145         * org.mlc.swing.layout.LayoutFrame(layoutConstraintsManager).setVisible(true);
146         */
147
148        panel.add( tfRules, "tfRules" );
149
150        addSeparator( "logs", "sep2" );
151        addLabel( "log file:", "lblSourceFile" );
152        panel.add( tfSourceFile, "tfSourceFile" );
153        panel.add( btSourceFile, "btSourceFile" );
154        panel.add( tfSourceStats, "tfSourceStats" );
155
156        addLabel( "output directory:", "lblOutputDir" );
157        panel.add( tfOutputDir, "tfOutputDir" );
158        panel.add( btOutputDir, "btOutputDir" );
159
160        JPanel bbar = ButtonBarFactory.buildRightAlignedBar( new JButton[] { btRun, btBatch } );
161        FormLayout layout = new FormLayout( "default:grow, 3dlu, pref" );
162        DefaultFormBuilder builder = new DefaultFormBuilder( layout );
163        builder.append( progress, bbar );
164        panel.add( builder.getPanel(), "pRun" );
165
166        addSeparator( "result", "sep3" );
167        panel.add( new JScrollPane( taResult ), "taResult" );
168
169        reloadThread = new Thread( this );
170        reloadThread.setDaemon( true );
171        reloadThread.setPriority( Thread.MIN_PRIORITY );
172        reloadThread.start();
173        return panel;
174    }
175
176    private void buildMenuBar()
177    {
178        JMenuBar menuBar = new JMenuBar();
179        // FILE MENU
180        JMenu fileMenu = new JMenu( "File" );
181        fileMenu.setMnemonic( KeyEvent.VK_F );
182
183        // Open file
184        JMenuItem fileOpenMenuItem = new JMenuItem( "Open rules file...", KeyEvent.VK_O );
185        fileOpenMenuItem.addActionListener( SwingAdapter.getActionListener( this, "btOpenActionPerformed" ) );
186        fileMenu.add( fileOpenMenuItem );
187
188        // New file
189        JMenuItem fileNewMenuItem = new JMenuItem( "New rules file...", KeyEvent.VK_N );
190        fileNewMenuItem.addActionListener( SwingAdapter.getActionListener( this, "btNewActionPerformed" ) );
191        fileMenu.add( fileNewMenuItem );
192
193        // exit
194        JMenuItem exitMenuItem = new JMenuItem( "Exit", KeyEvent.VK_X );
195        exitMenuItem.addActionListener( SwingAdapter.getActionListener( this, "exit" ) );
196        fileMenu.add( exitMenuItem );
197
198        // HELP MENU
199        JMenu helpMenu = new JMenu( "Help" );
200        helpMenu.setMnemonic( KeyEvent.VK_H );
201
202        // About
203        JMenuItem aboutMenuItem = new JMenuItem( "About...", KeyEvent.VK_A );
204        aboutMenuItem.addActionListener( SwingAdapter.getActionListener( this, "about" ) );
205        helpMenu.add( aboutMenuItem );
206
207        menuBar.add( fileMenu );
208        menuBar.add( helpMenu );
209        frame.setJMenuBar( menuBar );
210    }
211
212    public void about( ActionEvent ae )
213    {
214        JOptionPane.showMessageDialog( frame, ABOUT, "About", JOptionPane.INFORMATION_MESSAGE );
215    }
216
217    public void exit( ActionEvent ae )
218    {
219        System.exit( 0 );
220    }
221
222    private File currentDir = new File( "." );
223
224    private File configurationFile = null;
225
226    private long configurationFileLastModified;
227
228    public void btOpenActionPerformed( ActionEvent ae )
229    {
230        reloadConfigurationFileEnabled = false;
231        JFileChooser fc = new JFileChooser( currentDir );
232        int ret = fc.showOpenDialog( frame );
233        if ( ret == JFileChooser.APPROVE_OPTION )
234        {
235            selectConfigurationFile( fc.getSelectedFile() );
236        }
237        reloadConfigurationFileEnabled = true;
238    }
239
240    private LogDistiller ld = null;
241
242    public void selectConfigurationFile( File newConfiguration )
243    {
244        initProgress();
245        configurationFile = newConfiguration;
246        configurationFileLastModified = configurationFile.lastModified();
247        currentDir = configurationFile.getParentFile();
248        frame.setTitle( TITLE + ": " + configurationFile.getName() );
249
250        tfRules.setText( "reading " + configurationFile.getName() );
251        try
252        {
253            taResult.setText( IOUtils.toString( new XmlReader( configurationFile ) ) );
254            DOMConfigurator domConfigurator = new DOMConfigurator( new HashMap<String, String>() );
255            ld = domConfigurator.read( configurationFile, new GuiSAXErrorHandler() );
256            compatibilityUpdate( ld );
257
258            LogDistiller.LogType logtype = ld.getLogType();
259            String logeventType = ( logtype != null ) ? logtype.getId() : null;
260
261            // select logevent type and description according to file content
262            if ( logeventType != null )
263            {
264                if ( LogTypes.getLogType( logeventType ) == null )
265                {
266                    taResult.setText( "Unknown '" + logeventType + "' logtype.\n" + "Valid values: "
267                        + LogTypes.listAllLogTypeIds() );
268                    logeventType += " (unknown)";
269                }
270            }
271            else
272            {
273                logeventType = "undefined";
274                taResult.setText( "Undefined logtype.\n" + "Valid values: " + LogTypes.listAllLogTypeIds() );
275            }
276            try
277            {
278                LogDistillation distillation = new LogDistillation( ld );
279                logtypeDescription = distillation.getLogTypeDescription();
280            }
281            catch ( Exception e )
282            {
283                displayException( e );
284            }
285
286            // set the destination directory
287            selectOutputDir( new File( ld.getOutput().getDirectory() ) );
288
289            // set the rules text to a description of the rules read
290            Object[] args =
291                new Object[] { ld.getId(), ld.getDescription(), new Integer( ld.getGroups().length ),
292                    new Integer( ld.getCategories().length ), logeventType };
293            MessageFormat mf =
294                new MessageFormat( "{0} - {1}: {2,choice,0#no group|1#one group|1<{2,number,integer} groups}"
295                    + " {3,choice,0#with no category|1#in one category|1<in {3,number,integer} categories}"
296                    + ", logtype: {4}." );
297            tfRules.setText( mf.format( args ) );
298        }
299        catch ( RuntimeException re )
300        {
301            ld = null;
302            displayException( re );
303        }
304        catch ( IOException ioe )
305        {
306            ld = null;
307            displayException( ioe );
308        }
309        catch ( ParserConfigurationException pce )
310        {
311            ld = null;
312            displayException( pce );
313        }
314        catch ( SAXException se )
315        {
316            ld = null;
317            displayException( se );
318        }
319        updateBtRunState();
320    }
321
322    /**
323     * Make all that's possible to update latest LogDistiller model to maintain compatibility with older versions, since
324     * some elements have moved from Ant task to LogDistiller model.
325     *
326     * @param ld LogDistiller
327     */
328    private void compatibilityUpdate( LogDistiller ld )
329    {
330        if ( ld.getLogType() != null )
331        {
332            // ok, no compatibility upgrade to consider
333            return;
334        }
335        String logeventType = (String) ld.getOutput().getParams().get( "logevent.type" );
336        if ( logeventType != null )
337        {
338            String warning =
339                "parameter 'logevent.type' is deprecated, please use element 'logtype' from "
340                    + LogDistillerEntityResolver.LATEST_DTD + " in your rules file";
341            ld.addWarning( warning );
342            ld.buildCompatibilityLogType( logeventType );
343        }
344    }
345
346    private final Runnable runnableReloadConfigurationFile = SwingAdapter.getRunnable( this, "reloadConfigurationFile" );
347
348    private boolean reloadConfigurationFileEnabled = true;
349
350    public void reloadConfigurationFile()
351    {
352        selectConfigurationFile( configurationFile );
353        initProgress();
354    }
355
356    public class GuiSAXErrorHandler
357        implements ErrorHandler
358    {
359        public void error( SAXParseException spe )
360        {
361            taResult.append( "Parsing error on line " + spe.getLineNumber() + " and column " + spe.getColumnNumber()
362                + ": " + spe.getMessage() + "\n" );
363            Exception e = spe.getException();
364            if ( e != null )
365            {
366                displayException( e );
367            }
368        }
369
370        public void fatalError( SAXParseException spe )
371        {
372            error( spe );
373        }
374
375        public void warning( SAXParseException spe )
376        {
377            taResult.append( "Parsing warning on line " + spe.getLineNumber() + " and column " + spe.getColumnNumber()
378                + ": " + spe.getMessage() + "\n" );
379            Exception e = spe.getException();
380            if ( e != null )
381            {
382                displayException( e );
383            }
384        }
385    }
386
387    private void displayException( Throwable t )
388    {
389        StringWriter sw = new StringWriter();
390        PrintWriter out = new PrintWriter( sw );
391        t.printStackTrace( out );
392        out.close();
393        taResult.append( sw.toString() );
394    }
395
396    private LogType.Description logtypeDescription = null;
397
398    public void btNewActionPerformed( ActionEvent ae )
399    {
400        NewDialog dlg = new NewDialog( currentDir );
401        File newConfiguration = dlg.showNewConfigurationDialog( frame );
402        taResult.setText( "" );
403        if ( newConfiguration != null )
404        {
405            selectConfigurationFile( newConfiguration );
406        }
407    }
408
409    private File sourceFile = null;
410
411    private long sourceFileLastModified;
412
413    public void btSourceFileActionPerformed( ActionEvent ae )
414    {
415        JFileChooser fc = new JFileChooser( ( sourceFile == null ) ? currentDir : sourceFile.getParentFile() );
416        int ret = fc.showOpenDialog( frame );
417        if ( ret == JFileChooser.APPROVE_OPTION )
418        {
419            selectSourceFile( fc.getSelectedFile() );
420        }
421    }
422
423    public void selectSourceFile( File newSourceFile )
424    {
425        sourceFile = newSourceFile;
426        tfSourceFile.setText( sourceFile.getName() );
427        initProgress();
428        if ( !sourceFile.canRead() )
429        {
430            sourceFileLastModified = -1;
431            taResult.setText( "file not found: " + sourceFile.getAbsolutePath() );
432            return;
433        }
434        sourceFileLastModified = sourceFile.lastModified();
435        tfSourceStats.setText( "file size: " + FormatUtil.formatSize( sourceFile.length() ) );
436        progress.setMinimum( 0 );
437        progress.setLongMaximum( sourceFile.length() );
438        updateBtRunState();
439    }
440
441    private final Runnable runnableReloadSourceFile = SwingAdapter.getRunnable( this, "reloadSourceFile" );
442
443    public void reloadSourceFile()
444    {
445        setResultText( "" );
446        selectSourceFile( sourceFile );
447    }
448
449    private File outputDir = null;
450
451    public void btOutputDirActionPerformed( ActionEvent ae )
452    {
453        JFileChooser fc = new JFileChooser( ( outputDir == null ) ? currentDir : outputDir.getParentFile() );
454        fc.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY );
455        int ret = fc.showOpenDialog( frame );
456        if ( ret == JFileChooser.APPROVE_OPTION )
457        {
458            selectOutputDir( fc.getSelectedFile() );
459        }
460    }
461
462    private void selectOutputDir( File output )
463    {
464        outputDir = output;
465        tfOutputDir.setText( outputDir.getAbsolutePath() );
466        initProgress();
467        updateBtRunState();
468    }
469
470    private void updateBtRunState()
471    {
472        boolean enabled =
473            ( ld != null ) && ( logtypeDescription != null ) && ( sourceFile != null ) && ( outputDir != null );
474        btRun.setEnabled( enabled );
475        btBatch.setEnabled( enabled );
476    }
477
478    private String resultText = "";
479
480    public void setResultText()
481    {
482        taResult.setText( resultText );
483    }
484
485    private final Runnable runnableSetResultText = SwingAdapter.getRunnable( this, "setResultText" );
486
487    /**
488     * set the result text, taking into account if we are on the event dispatch thread or not.
489     *
490     * @param text the text to display
491     */
492    private void setResultText( String text )
493    {
494        this.resultText = text;
495        if ( SwingUtilities.isEventDispatchThread() )
496        {
497            taResult.setText( resultText );
498        }
499        else
500        {
501            SwingUtilities.invokeLater( runnableSetResultText );
502        }
503    }
504
505    private long progressValue = 0;
506
507    private int progressCount = 0;
508
509    private String progressText = null;
510
511    private final Runnable runnableUpdateProgress = SwingAdapter.getRunnable( this, "updateProgress" );
512
513    public void updateProgress()
514    {
515        if ( progressText == null )
516        {
517            progress.setValue( progress.getMaximum() );
518            progress.setString( "processed " + progressValue + " logevents" );
519        }
520        else
521        {
522            progress.setLongValue( progressValue );
523            progress.setString( progressCount + " - " + progressText );
524        }
525    }
526
527    private void initProgress()
528    {
529        progress.setString( "0%" );
530        progress.setValue( 0 );
531    }
532
533    private final Runnable runnableRunLogDistillation = SwingAdapter.getRunnable( this, "runLogDistillation" );
534
535    public void runLogDistillation()
536    {
537        try
538        {
539            LogEvent.Factory factory =
540                logtypeDescription.newFactory( new InputStreamReader( new FileInputStream( sourceFile ) ),
541                                               sourceFile.toURL().toString() );
542            ld.getOutput().setDirectory( outputDir.getAbsolutePath() );
543            ld.getOutput().setContent( "interactive run" );
544            LogDistillation distillation = new LogDistillation( ld );
545            distillation.begin();
546            LogEvent le;
547            long lastProgress = System.currentTimeMillis();
548            while ( ( le = factory.nextEvent() ) != null )
549            {
550                distillation.processLogEvent( le );
551                long time = System.currentTimeMillis();
552                if ( ( time - lastProgress ) > 100 )
553                {
554                    progressValue = distillation.getEventBytes();
555                    progressCount = distillation.getEventCount();
556                    progressText = le.getTimestamp();
557                    SwingUtilities.invokeLater( runnableUpdateProgress );
558                    lastProgress = time;
559                }
560            }
561            distillation.end();
562            progressValue = distillation.getEventCount();
563            progressText = null;
564            SwingUtilities.invokeLater( runnableUpdateProgress );
565
566            StringWriter sw = new StringWriter();
567            new TextReport().report( distillation, sw );
568            setResultText( sw.toString() );
569        }
570        catch ( RuntimeException re )
571        {
572            ld = null;
573            displayException( re );
574        }
575        catch ( IOException ioe )
576        {
577            ld = null;
578            displayException( ioe );
579        }
580        catch ( ParseException pe )
581        {
582            ld = null;
583            displayException( pe );
584        }
585        setAllButtonsEnabled( true );
586        reloadConfigurationFileEnabled = true;
587    }
588
589    public void btRunActionPerformed( ActionEvent ae )
590    {
591        reloadConfigurationFileEnabled = false;
592        setAllButtonsEnabled( false );
593        taResult.setText( "" );
594        new Thread( runnableRunLogDistillation ).start();
595    }
596
597    public void btBatchActionPerformed( ActionEvent ae )
598    {
599        taResult.setText( "" );
600        initProgress();
601        try
602        {
603            String text = IOUtils.toString( MainPanel.class.getResourceAsStream( "reference-build.xml" ), "UTF-8" );
604            Map<String, String> properties = new HashMap<String, String>();
605            properties.put( "id", ld.getId() );
606            properties.put( "configurationFile", configurationFile.getPath() );
607            properties.put( "sourceDir", sourceFile.getAbsoluteFile().getParentFile().getPath() );
608            properties.put( "sourceFile", sourceFile.getName() + "*" );
609            properties.put( "destDir", outputDir.getPath() );
610            PropertiesReplacer repl = new PropertiesReplacer( properties, "#(", ")" );
611            taResult.setText( repl.replaceProperties( text ) );
612        }
613        catch ( Exception e )
614        {
615            displayException( e );
616        }
617    }
618
619    public void setAllButtonsEnabled( boolean enable )
620    {
621        btBatch.setEnabled( enable );
622        btOutputDir.setEnabled( enable );
623        btRun.setEnabled( enable );
624        btSourceFile.setEnabled( enable );
625        if ( enable )
626        {
627            updateBtRunState();
628        }
629    }
630
631    /**
632     * Reload task: checks every second if configuration file or source file has changed.
633     */
634    public void run()
635    {
636        while ( true )
637        {
638            try
639            {
640                Thread.sleep( 1000 );
641            }
642            catch ( InterruptedException ie )
643            {
644                ie.printStackTrace();
645            }
646            if ( reloadConfigurationFileEnabled && ( configurationFile != null )
647                && ( configurationFileLastModified != configurationFile.lastModified() ) )
648            {
649                try
650                {
651                    SwingUtilities.invokeAndWait( runnableReloadConfigurationFile );
652                }
653                catch ( InvocationTargetException ite )
654                {
655                    displayException( ite );
656                }
657                catch ( InterruptedException ie )
658                {
659                    displayException( ie );
660                }
661            }
662            if ( ( sourceFile != null ) && ( sourceFileLastModified != sourceFile.lastModified() ) )
663            {
664                try
665                {
666                    SwingUtilities.invokeAndWait( runnableReloadSourceFile );
667                }
668                catch ( InvocationTargetException ite )
669                {
670                    displayException( ite );
671                }
672                catch ( InterruptedException ie )
673                {
674                    displayException( ie );
675                }
676            }
677        }
678    }
679}