001package net.sf.logdistiller;
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.io.IOException;
018import java.io.PrintWriter;
019import java.io.StringWriter;
020import java.text.ParseException;
021import java.util.regex.Matcher;
022
023import net.sf.logdistiller.LogType.AttributeInfo;
024
025import org.apache.commons.lang.ArrayUtils;
026
027/**
028 * Log events base class. Every new log type must implement an inherited class, with appropriate:
029 * <ul>
030 * <li>{@link Factory} to create events from the log stream</li>
031 * <li>{@link LogType} to declare the log type and eventually available parameters to customize its format</li>
032 * </ul>
033 *
034 * @see LogType
035 * @see LogTypes
036 */
037public abstract class LogEvent
038{
039    private final Factory factory;
040
041    private final String rawLog;
042
043    /** provided attributes */
044    private String[] attributes;
045
046    /** extended attributes */
047    private Extension[] extensions;
048
049    protected LogEvent( Factory factory, String rawLog )
050    {
051        this.factory = factory;
052        this.rawLog = rawLog;
053        this.extensions = new Extension[factory.getDescription().getExtensions().length];
054    }
055
056    /**
057     * Set the provided attributes values.
058     *
059     * @param attributes attributes values
060     */
061    protected void setAttributes( String[] attributes )
062    {
063        int count = factory.getDescription().getAttributesCount();
064        if ( attributes.length != count )
065        {
066            throw new IllegalArgumentException( "expected " + count + " attributes, got " + attributes.length );
067        }
068        this.attributes = attributes;
069    }
070
071    protected void checkInitialized()
072    {
073        if ( attributes == null )
074        {
075            throw new IllegalStateException( "log event not initialized: call setAttributes() to initialize it" );
076        }
077    }
078
079    /**
080     * Get the raw log text from which this event was parsed.
081     *
082     * @return String
083     */
084    public String getRawLog()
085    {
086        return rawLog;
087    }
088
089    /**
090     * Get a provided attribute value by its position index.
091     *
092     * @param pos the position index of the attribute.
093     * @return the value of the attribute.
094     */
095    public String getAttribute( int pos )
096    {
097        checkInitialized();
098        return attributes[pos];
099    }
100
101    /**
102     * Get a clone copy of the provided attributes. Note the this method clones the values array.
103     *
104     * @return (a copy of) provided attributes
105     */
106    public String[] getAttributes()
107    {
108        checkInitialized();
109        return (String[]) ArrayUtils.clone( attributes );
110    }
111
112    /**
113     * Get provided attributes count.
114     *
115     * @return the count of provided attributes
116     */
117    public int getAttributesCount()
118    {
119        checkInitialized();
120        return attributes.length;
121    }
122
123    /**
124     * Extracts an attribute's provided value of the log event.
125     *
126     * @param attributeName the name of the attribute (provided only)
127     * @return the value
128     */
129    public String getAttribute( String attributeName )
130    {
131        int index = factory.getDescription().getAttributeIndex( attributeName );
132        if ( index < 0 )
133        {
134            throw new IllegalArgumentException( "unknown provided attribute '" + attributeName + "'" );
135        }
136        return getAttribute( index );
137    }
138
139    public String getValue( AttributeInfo info )
140    {
141        if ( info.extended )
142        {
143            Extension extension = extensions[info.index];
144            if ( extension == null )
145            {
146                extension = new Extension( factory.getDescription().getExtensions()[info.index] );
147                extensions[info.index] = extension;
148            }
149            return extension.getValue( info.regexpGroup );
150        }
151        return getAttribute( info.index );
152    }
153
154    public Factory getFactory()
155    {
156        return factory;
157    }
158
159    public void dump( PrintWriter out )
160    {
161        out.println( "- raw log: " + getRawLog() );
162        for ( int i = 0; i < getAttributesCount(); i++ )
163        {
164            out.println( "- " + getFactory().getDescription().getAttributeName( i ) + ": " + getAttribute( i ) );
165        }
166    }
167
168    public String dump()
169    {
170        StringWriter sw = new StringWriter();
171        PrintWriter out = new PrintWriter( sw, false );
172        dump( out );
173        out.close();
174        return sw.toString();
175    }
176
177    /**
178     * Get the timestamp.
179     *
180     * @return the timestamp or "-" if no this log type does not have a timestamp
181     * @see LogType.Description#getTimestampAttribute()
182     */
183    public String getTimestamp()
184    {
185        int index = factory.getDescription().getTimestampAttribute();
186        return ( index > 0 ) ? getAttribute( index ) : "-";
187    }
188
189    /**
190     * The base class for LogEvent factories, responsible for parsing an input stream into <code>LogEvent</code>s.
191     */
192    public static abstract class Factory
193    {
194        public final static String NEWLINE = System.getProperty( "line.separator" );
195
196        protected final LogType.Description description;
197
198        protected final String logSource;
199
200        private LogEvent lastEvent;
201
202        private LogEvent pushedbackEvent;
203
204        protected Factory( LogType.Description description, String logSource )
205        {
206            this.description = description;
207            this.logSource = logSource;
208        }
209
210        /**
211         * gets the next LogEvent.
212         *
213         * @throws IOException
214         * @throws ParseException
215         * @return LogEvent the new LogEvent read, or <code>null</code> if none available any more
216         */
217        public LogEvent nextEvent()
218            throws IOException, ParseException
219        {
220            LogEvent nextEvent = null;
221            if ( pushedbackEvent != null )
222            {
223                nextEvent = pushedbackEvent;
224                pushedbackEvent = null;
225            }
226            else
227            {
228                try
229                {
230                    nextEvent = readNextEvent();
231                }
232                catch ( ParseException pe )
233                {
234                    ParseException pe2 = new ParseException( "error in '" + logSource + "' " + pe.getMessage(),
235                                                             pe.getErrorOffset() );
236                    pe2.initCause( pe );
237                    throw pe2;
238                }
239                catch ( RuntimeException re )
240                {
241                    throw new RuntimeException( "error in '" + logSource + "' " + re.getMessage(), re );
242                }
243            }
244            lastEvent = nextEvent;
245            return nextEvent;
246        }
247
248        public void pushbackLastEvent()
249            throws IllegalStateException
250        {
251            if ( pushedbackEvent != null )
252            {
253                throw new IllegalStateException( "The last event has already been pushed back" );
254            }
255            if ( lastEvent == null )
256            {
257                throw new IllegalStateException( "There is no last event to push back" );
258            }
259            pushedbackEvent = lastEvent;
260        }
261
262        public LogType.Description getDescription()
263        {
264            return description;
265        }
266
267        public String getLogSource()
268        {
269            return logSource;
270        }
271
272        protected abstract LogEvent readNextEvent()
273            throws IOException, ParseException;
274    }
275
276    /**
277     * Extended attributes calculated for a log event.
278     *
279     * @since 1.1
280     */
281    private class Extension
282    {
283        /**
284         * Values of regexp match groups
285         */
286        private final String[] values;
287
288        public Extension( Attributes.Extension definition )
289        {
290            values = new String[definition.getProvides().size()];
291            Matcher matcher = definition.getRegexp().matcher( getAttribute( definition.getSource() ) );
292            if ( matcher.find() )
293            {
294                int count = matcher.groupCount();
295                for ( int i = 0; i < values.length; i++ )
296                {
297                    String value = ( i < count ) ? matcher.group( i + 1 ) : "";
298                    values[i] = ( value == null ) ? "" : value;
299                }
300            }
301            else
302            {
303                for ( int i = 0; i < values.length; i++ )
304                {
305                    values[i] = "";
306                }
307            }
308        }
309
310        public String getValue( int index )
311        {
312            return values[index];
313        }
314    }
315}