001    package 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    
017    import java.io.IOException;
018    import java.io.PrintWriter;
019    import java.io.StringWriter;
020    import java.text.ParseException;
021    import java.util.regex.Matcher;
022    
023    import net.sf.logdistiller.LogType.AttributeInfo;
024    
025    import 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     */
037    public 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    }