001    package net.sf.logdistiller.logtypes;
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.io.*;
018    import java.text.ParseException;
019    import java.util.Arrays;
020    import java.util.Map;
021    
022    import org.apache.commons.lang.StringUtils;
023    
024    import net.sf.logdistiller.util.BufferingReader;
025    import net.sf.logdistiller.util.csv.CSVParse;
026    import net.sf.logdistiller.util.csv.CSVParser;
027    
028    import net.sf.logdistiller.LogEvent;
029    import net.sf.logdistiller.LogType;
030    
031    /**
032     * A CSV (see <a href="http://tools.ietf.org/html/rfc4180">RFC-4180</a>) log parser, based on <a
033     * href="http://ostermiller.org/utils/CSV.html">OstermillerUtils</a>. CSV log type supports following parameters:
034     * <ul>
035     * <li><code>attributes</code>: specify the column names (and of course count), starting with <code>logSource</code></li>
036     * <li><code>delimiter</code>: set the column delimiter (default value: <code>','</code>)</li>
037     * <li><code>quote</code>: set the quoting character (default value: <code>'"'</code>)</li>
038     * <li><code>commentStart</code>: set the list of characters a comment line may start with (default value: none)</li>
039     * <!--li><code>escapes</code></li-->
040     * </ul>
041     * By default, the classification rules generated by the GUI for this type of logs won't be able to sort events, as the
042     * log event structure is too generic.
043     *
044     * @see CSVParser
045     * @since 0.9
046     */
047    public class CSVLogEvent
048        extends LogEvent
049    {
050        public final static String ID = "csv";
051    
052        public final static LogType LOGTYPE = new CSVLogType();
053    
054        public CSVLogEvent( Factory factory, String rawLog, String[] values )
055            throws ParseException
056        {
057            super( factory, rawLog );
058            int count = factory.getDescription().getAttributesCount();
059            String[] attr = new String[count];
060            Arrays.fill( attr, "" );
061            attr[0] = factory.getLogSource();
062            System.arraycopy( values, 0, attr, 1, Math.min( values.length, count - 1 ) );
063            setAttributes( attr );
064        }
065    
066        private static class CSVLogType
067            extends LogType
068        {
069            public CSVLogType()
070            {
071                super( CSVLogEvent.ID );
072            }
073    
074            public LogType.Description newDescription( Map params )
075            {
076                String attributes = (String) params.get( "attributes" );
077                attributes = ( attributes == null ) ? "" : attributes;
078                if ( !attributes.startsWith( "logSource," ) )
079                {
080                    attributes = "logSource," + attributes;
081                }
082                String[] attributeNames = StringUtils.split( attributes, ", " );
083                String attr = (String) params.get( "delimiter" );
084                char delimiter = ( attr == null ) ? ',' : attr.charAt( 0 );
085                attr = (String) params.get( "quote" );
086                char quote = ( attr == null ) ? '"' : attr.charAt( 0 );
087                String commentStart = (String) params.get( "commentStart" );
088                commentStart = ( commentStart == null ) ? "" : commentStart;
089                return new CSVLogEvent.Description( this, attributeNames, delimiter, quote, commentStart );
090            }
091        }
092    
093        private static class Description
094            extends LogType.Description
095        {
096            private final char delimiter;
097    
098            private final char quote;
099    
100            private final String commentStart;
101    
102            public Description( LogType logtype, String[] attributeNames, char delimiter, char quote, String commentStart )
103            {
104                super( logtype, attributeNames );
105                this.delimiter = delimiter;
106                this.quote = quote;
107                this.commentStart = commentStart;
108            }
109    
110            public LogEvent.Factory newFactory( Reader reader, String logSource )
111                throws IOException
112            {
113                return new Factory( this, reader, logSource );
114            }
115    
116            public CSVParse newCSVParser( Reader reader )
117            {
118                CSVParser parser = new CSVParser( reader, delimiter );
119                parser.changeQuote( quote );
120                parser.setCommentStart( commentStart );
121                return parser;
122            }
123    
124            public int getTimestampAttribute()
125            {
126                return -1;
127            }
128        }
129    
130        private static class Factory
131            extends LogEvent.Factory
132        {
133            private final BufferingReader buffering;
134    
135            private final CSVParse parser;
136    
137            public Factory( Description description, Reader reader, String logSource )
138                throws FileNotFoundException
139            {
140                super( description, logSource );
141                buffering = new BufferingReader( reader );
142                this.parser = description.newCSVParser( buffering );
143            }
144    
145            protected LogEvent readNextEvent()
146                throws IOException, ParseException
147            {
148                int before = parser.getLastCharCount();
149                String[] curLine = parser.getLine();
150                if ( curLine == null )
151                {
152                    // EOF
153                    return null;
154                }
155                int after = parser.getLastCharCount();
156                String rawLog = StringUtils.chomp( buffering.freeData( after - before ) );
157                return new CSVLogEvent( this, rawLog, curLine );
158            }
159        }
160    }