001    package 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    
017    import java.awt.Font;
018    import java.awt.LayoutManager;
019    import java.awt.event.ActionEvent;
020    import java.awt.event.KeyEvent;
021    import java.io.*;
022    import java.lang.reflect.*;
023    import java.text.MessageFormat;
024    import java.text.ParseException;
025    import java.util.*;
026    import javax.swing.*;
027    import javax.xml.parsers.ParserConfigurationException;
028    
029    import org.xml.sax.ErrorHandler;
030    import org.xml.sax.SAXException;
031    import org.xml.sax.SAXParseException;
032    
033    import com.jgoodies.forms.builder.DefaultFormBuilder;
034    import com.jgoodies.forms.factories.ButtonBarFactory;
035    import com.jgoodies.forms.factories.DefaultComponentFactory;
036    import com.jgoodies.forms.layout.FormLayout;
037    import com.sun.syndication.io.XmlReader;
038    
039    import org.apache.commons.io.IOUtils;
040    
041    import net.sf.logdistiller.LogDistillation;
042    import net.sf.logdistiller.LogDistiller;
043    import net.sf.logdistiller.LogEvent;
044    import net.sf.logdistiller.LogType;
045    import net.sf.logdistiller.LogTypes;
046    import net.sf.logdistiller.reports.TextReport;
047    import net.sf.logdistiller.util.FormatUtil;
048    import net.sf.logdistiller.util.PropertiesReplacer;
049    import net.sf.logdistiller.xml.DOMConfigurator;
050    import net.sf.logdistiller.xml.LogDistillerEntityResolver;
051    
052    /**
053     * Main gui panel.
054     */
055    public 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 Properties() );
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 properties = new HashMap();
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    }