001    package net.sf.logdistiller.xml;
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.util.*;
019    import javax.xml.parsers.DocumentBuilder;
020    import javax.xml.parsers.DocumentBuilderFactory;
021    import javax.xml.parsers.ParserConfigurationException;
022    import org.w3c.dom.Document;
023    import org.w3c.dom.Element;
024    import org.xml.sax.InputSource;
025    import org.xml.sax.SAXException;
026    
027    import net.sf.logdistiller.*;
028    import net.sf.logdistiller.util.PropertiesReplacer;
029    import org.xml.sax.ErrorHandler;
030    
031    /**
032     * This class parses an XML rules configuration file and creates corresponding LogDistiller object.
033     *
034     * @see net.sf.logdistiller.LogDistiller
035     */
036    public class DOMConfigurator
037    {
038        private final Map overriddenProperties;
039    
040        private PropertiesReplacer replacer = null;
041    
042        private boolean workInProgress = false;
043    
044        private boolean old;
045    
046        private List warnings = null;
047    
048        private LogDistiller ld = null;
049    
050        public DOMConfigurator()
051        {
052            overriddenProperties = null;
053        }
054    
055        public DOMConfigurator( Map overriddenProperties )
056        {
057            this.overriddenProperties = overriddenProperties;
058        }
059    
060        synchronized public LogDistiller read( File f )
061            throws ParserConfigurationException, IOException, SAXException
062        {
063            return read( f, null );
064        }
065    
066        synchronized public LogDistiller read( File f, ErrorHandler errorHandler )
067            throws ParserConfigurationException, IOException, SAXException
068        {
069            return read( new FileInputStream( f ), errorHandler );
070        }
071    
072        synchronized public LogDistiller read( InputStream in )
073            throws ParserConfigurationException, IOException, SAXException
074        {
075            return read( in, null );
076        }
077    
078        synchronized public LogDistiller read( InputStream in, ErrorHandler errorHandler )
079            throws ParserConfigurationException, IOException, SAXException
080        {
081            return read( new InputSource( in ), errorHandler );
082        }
083    
084        synchronized public LogDistiller read( InputSource inputSource )
085            throws ParserConfigurationException, IOException, SAXException
086        {
087            return read( inputSource, null );
088        }
089    
090        /**
091         * begin configuration, if not currently in progress
092         *
093         * @return boolean true if really begin, false if configuration is already in progress
094         */
095        protected boolean begin()
096        {
097            if ( workInProgress )
098            {
099                // parse() method is called by read()
100                return false;
101            }
102            workInProgress = true;
103            warnings = new ArrayList();
104            old = false;
105            return true;
106        }
107    
108        protected void end( boolean really )
109        {
110            if ( !really )
111            {
112                return;
113            }
114            workInProgress = false;
115            if ( ld != null )
116            {
117                Iterator iter = warnings.iterator();
118                while ( iter.hasNext() )
119                {
120                    ld.addWarning( (String) iter.next() );
121                }
122            }
123            warnings = null;
124            ld = null;
125        }
126    
127        protected void addWarning( String warning )
128        {
129            warnings.add( warning );
130        }
131    
132        synchronized public LogDistiller read( InputSource inputSource, ErrorHandler errorHandler )
133            throws ParserConfigurationException, IOException, SAXException
134        {
135            boolean beginning = begin();
136            try
137            {
138                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
139                dbf.setValidating( true );
140                DocumentBuilder docBuilder = dbf.newDocumentBuilder();
141                docBuilder.setErrorHandler( ( errorHandler == null ) ? new SAXErrorHandler() : errorHandler );
142                LogDistillerEntityResolver resolver = new LogDistillerEntityResolver();
143                docBuilder.setEntityResolver( resolver );
144                Document doc = docBuilder.parse( inputSource );
145    
146                if ( resolver.getCurrentDtd() == null )
147                {
148                    addWarning( "no DTD used: you should use " + LogDistillerEntityResolver.LATEST_DTD_URL );
149                }
150                else if ( !LogDistillerEntityResolver.LATEST_DTD.equals( resolver.getCurrentDtd() ) )
151                {
152                    addWarning( "using old DTD " + resolver.getCurrentDtd() + ": you should consider upgrading to "
153                        + LogDistillerEntityResolver.LATEST_DTD + " (please read upgrade documentation)" );
154                }
155    
156                ld = parse( doc.getDocumentElement() );
157                return ld;
158            }
159            finally
160            {
161                end( beginning );
162            }
163        }
164    
165        synchronized public LogDistiller parse( Element element )
166        {
167            boolean beginning = begin();
168            try
169            {
170                if ( !"logdistiller".equals( element.getNodeName() ) )
171                {
172                    throw new IllegalArgumentException( "document root element is '" + element.getNodeName()
173                        + "' where it should be 'logdistiller': see " + LogDistillerEntityResolver.LATEST_DTD_URL );
174                }
175    
176                String id = element.getAttribute( "id" );
177                Map properties = parseProperties( DOMUtils.getChildElementsByTagName( element, "property" ) );
178                replacer = new PropertiesReplacer( properties );
179                String description = replacer.replaceProperties( DOMUtils.getPCDATAByTagName( element, "description" ) );
180                LogDistiller.LogType logtype = null;
181                LogDistiller.Output output = null;
182    
183                Iterator iter = DOMUtils.getChildElementsByTagName( element, "logtype" );
184                if ( iter.hasNext() )
185                { // logtype element appeared in logdistiller-1_3.dtd only
186                    logtype = parseLogType( (Element) iter.next() );
187                    output = parseOutput( (Element) DOMUtils.getChildElementsByTagName( element, "output" ).next() );
188                }
189                else
190                {
191                    old = true;
192                    Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
193                    String logsUrl = (String) params.get( "logs.url" );
194                    output =
195                        new LogDistiller.Output( "logs", logsUrl, "default", "", params, buildCompatibilityReports( params ) );
196                }
197    
198                LogDistiller.Category[] categories =
199                    parseCategories( DOMUtils.getChildElementsByTagName( element, "category" ) );
200                LogDistiller.Group[] groups =
201                    parseGroups( DOMUtils.getChildElementsByTagName( element, "group" ), categories );
202                return new LogDistiller( id, description, logtype, output, categories, groups, old );
203            }
204            finally
205            {
206                end( beginning );
207            }
208        }
209    
210        protected Map parseProperties( Iterator iter )
211        {
212            Map map = new HashMap();
213            while ( iter.hasNext() )
214            {
215                Element param = (Element) iter.next();
216                String name = param.getAttribute( "name" );
217                String value = param.getAttribute( "value" );
218                if ( map.put( name, value ) != null )
219                {
220                    addWarning( "multiple values were defined for '" + name
221                        + "' property: the last one will override other values." );
222                }
223            }
224            if ( overriddenProperties != null )
225            {
226                // override properties set by this configurator
227                Iterator iter2 = overriddenProperties.keySet().iterator();
228                while ( iter2.hasNext() )
229                {
230                    String name = (String) iter2.next();
231                    if ( map.put( name, overriddenProperties.get( name ) ) == null )
232                    {
233                        throw new IllegalArgumentException( "unable to override property '" + name + "': unknown property" );
234                    }
235                }
236            }
237            return map;
238        }
239    
240        protected LogDistiller.LogType parseLogType( Element element )
241        {
242            String id = element.getAttribute( "id" );
243            Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
244            String paramAttributes = (String)params.get( "attributes" );
245    
246            Attributes attributes = null;
247            Iterator iter = DOMUtils.getChildElementsByTagName( element, "attributes" );
248            if ( iter.hasNext() )
249            { // attributes element appeared in logdistiller-1_4.dtd only
250                attributes = parseAttributes( paramAttributes, (Element) iter.next() );
251            }
252            else
253            {
254                attributes = new Attributes( paramAttributes, Attributes.NO_EXTENSIONS );
255                addWarning( "no logtype/attributes element defined: please consider adding this element (new in LogDistiller 1.1)." );
256            }
257            return new LogDistiller.LogType( id, params, attributes );
258        }
259    
260        protected Attributes parseAttributes( String paramAttributes, Element element )
261        {
262            String provided = DOMUtils.getPCDATAByTagName( element, "provided" );
263            if ( provided == null )
264            {
265                addWarning( "logtype 'attributes' param has been replaced by <provided> element: please consider using this element (new in LogDistiller 1.1)" );
266                provided = paramAttributes;
267            }
268            Attributes.Extension[] extensions = parseExtensions( DOMUtils.getChildElementsByTagName( element, "extension" ) );
269            return new Attributes( provided, extensions );
270        }
271    
272        protected Attributes.Extension[] parseExtensions( Iterator iter )
273        {
274            List extensions = new ArrayList();
275            while ( iter.hasNext() )
276            {
277                Element extension = (Element) iter.next();
278                extensions.add( parseExtension( extension ) );
279            }
280            return (Attributes.Extension[]) extensions.toArray( new Attributes.Extension[extensions.size()] );
281        }
282    
283        protected Attributes.Extension parseExtension( Element element )
284        {
285            String source = element.getAttribute( "source" );
286            String provides = element.getAttribute( "provides" );
287            String regexp = DOMUtils.getPCDATA( element );
288            return new Attributes.Extension( source, provides, regexp );
289        }
290    
291        protected LogDistiller.Output parseOutput( Element element )
292        {
293            String directory = replacer.replaceProperties( element.getAttribute( "directory" ) );
294            String url = replacer.replaceProperties( element.getAttribute( "url" ) );
295            String content = replacer.replaceProperties( element.getAttribute( "content" ) );
296            String skip = replacer.replaceProperties( element.getAttribute( "skip" ) );
297            Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
298    
299            String paramLogsUrl = (String) params.get( "logs.url" );
300            if ( paramLogsUrl != null )
301            {
302                if ( url == null )
303                {
304                    url = paramLogsUrl;
305                    addWarning( "in output element, param logs.url is now deprecated: please use url attribute (<output url=\"...\">)" );
306                }
307                else
308                {
309                    addWarning( "in output element, attribute url and deprecated param logs.url have both been set: ignoring param" );
310                }
311            }
312    
313            LogDistiller.Report[] reports = parseReports( DOMUtils.getChildElementsByTagName( element, "report" ) );
314    
315            return new LogDistiller.Output( directory, url, content, skip, params, reports );
316        }
317    
318        protected LogDistiller.Report[] parseReports( Iterator iter )
319        {
320            List reports = new ArrayList();
321            while ( iter.hasNext() )
322            {
323                Element report = (Element) iter.next();
324                reports.add( parseReport( report ) );
325            }
326            return (LogDistiller.Report[]) reports.toArray( new LogDistiller.Report[reports.size()] );
327        }
328    
329        protected LogDistiller.Report parseReport( Element element )
330        {
331            String publisher = replacer.replaceProperties( element.getAttribute( "publisher" ) );
332            String format = replacer.replaceProperties( element.getAttribute( "format" ) );
333            Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
334            return new LogDistiller.Report( publisher, format, params );
335        }
336    
337        protected LogDistiller.Report[] buildCompatibilityReports( Map params )
338        {
339            LogDistiller.Report fileReport = new LogDistiller.Report( "file", "txt", new HashMap() );
340            String to = (String) params.get( "mail.to" );
341            if ( to == null )
342            {
343                return new LogDistiller.Report[] { fileReport };
344            }
345            Map newParams = new HashMap();
346            newParams.put( "to", replacer.replaceProperties( to ) );
347            String cc = (String) params.get( "mail.cc" );
348            if ( cc != null )
349            {
350                newParams.put( "cc", replacer.replaceProperties( cc ) );
351            }
352            LogDistiller.Report mailReport = new LogDistiller.Report( "mail", "txt", newParams );
353            return new LogDistiller.Report[] { fileReport, mailReport };
354        }
355    
356        protected Map parseParams( Iterator iter )
357        {
358            Map params = new HashMap();
359            while ( iter.hasNext() )
360            {
361                Element param = (Element) iter.next();
362                String name = param.getAttribute( "name" );
363                String value = replacer.replaceProperties( DOMUtils.getPCDATA( param ) );
364                if ( params.put( name, value ) != null )
365                {
366                    addWarning( "multiple values were defined for '" + name
367                        + "' parameter: the last one will override other values." );
368                }
369            }
370            return params;
371        }
372    
373        protected LogDistiller.Category[] parseCategories( Iterator iter )
374        {
375            List categories = new ArrayList();
376            while ( iter.hasNext() )
377            {
378                Element category = (Element) iter.next();
379                String id = category.getAttribute( "id" );
380                String description = DOMUtils.getPCDATA( category );
381                categories.add( new LogDistiller.Category( id, description ) );
382            }
383            return (LogDistiller.Category[]) categories.toArray( new LogDistiller.Category[categories.size()] );
384        }
385    
386        protected LogDistiller.Group[] parseGroups( Iterator iter, LogDistiller.Category[] categories )
387        {
388            Map mapCategories = new HashMap();
389            for ( int i = 0; i < categories.length; i++ )
390            {
391                LogDistiller.Category category = categories[i];
392                mapCategories.put( category.getId(), category );
393            }
394            List groups = new ArrayList();
395            while ( iter.hasNext() )
396            {
397                Element group = (Element) iter.next();
398                groups.add( parseGroup( group, mapCategories ) );
399            }
400            return (LogDistiller.Group[]) groups.toArray( new LogDistiller.Group[groups.size()] );
401        }
402    
403        protected LogDistiller.Group parseGroup( Element element, Map categories )
404        {
405            String id = element.getAttribute( "id" );
406            String description = replacer.replaceProperties( DOMUtils.getPCDATAByTagName( element, "description" ) );
407            String attr = replacer.replaceProperties( element.getAttribute( "continueProcessing" ) );
408            boolean continueProcessing = Boolean.valueOf( ( attr == null ) ? "false" : attr ).booleanValue();
409            attr = element.getAttribute( "save" );
410            if ( attr == null )
411            {
412                attr = "true";
413            }
414            boolean save = !"false".equals( attr );
415            Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
416            Condition[] conditions = parseConditions( DOMUtils.getChildElementsByTagName( element, "condition" ) );
417            LogDistiller.Report[] reports = parseReports( DOMUtils.getChildElementsByTagName( element, "report" ) );
418            LogDistiller.Plugin[] plugins = parsePlugins( DOMUtils.getChildElementsByTagName( element, "plugin" ) );
419            if ( old )
420            {
421                reports = buildCompatibilityReports( params );
422            }
423            LogDistiller.Category category = (LogDistiller.Category) categories.get( element.getAttribute( "category" ) );
424            return new LogDistiller.Group( id, description, continueProcessing, save, params, conditions, reports, plugins,
425                                           category );
426        }
427    
428        protected Condition[] parseConditions( Iterator iter )
429        {
430            List conditions = new ArrayList();
431            while ( iter.hasNext() )
432            {
433                Element condition = (Element) iter.next();
434                conditions.add( parseCondition( condition ) );
435            }
436            return (Condition[]) conditions.toArray( new Condition[conditions.size()] );
437        }
438    
439        protected Condition parseCondition( Element element )
440        {
441            String tags = element.getAttribute( "tags" );
442            Match[] matches = parseMatchs( DOMUtils.getChildElementsByTagName( element, "match" ) );
443            return new Condition( tags, matches );
444        }
445    
446        protected Match[] parseMatchs( Iterator iter )
447        {
448            List matchs = new ArrayList();
449            while ( iter.hasNext() )
450            {
451                Element match = (Element) iter.next();
452                matchs.add( parseMatch( match ) );
453            }
454            return (Match[]) matchs.toArray( new Match[matchs.size()] );
455        }
456    
457        protected Match parseMatch( Element element )
458        {
459            String attribute = element.getAttribute( "attribute" );
460            String type = element.getAttribute( "type" );
461            String reference = replacer.replaceProperties( DOMUtils.getPCDATA( element ) );
462            return new Match( attribute, type, reference );
463        }
464    
465        protected LogDistiller.Plugin[] parsePlugins( Iterator iter )
466        {
467            List plugins = new ArrayList();
468            while ( iter.hasNext() )
469            {
470                Element plugin = (Element) iter.next();
471                plugins.add( parsePlugin( plugin ) );
472            }
473            return (LogDistiller.Plugin[]) plugins.toArray( new LogDistiller.Plugin[plugins.size()] );
474        }
475    
476        protected LogDistiller.Plugin parsePlugin( Element element )
477        {
478            String type = element.getAttribute( "type" );
479            if ( Plugins.getPlugin( type ) == null )
480            {
481                throw new PluginConfigException( "plugin type '" + type + "' unknown, valid values: "
482                    + Plugins.listAllPluginIds() );
483            }
484            String attr = element.getAttribute( "globalReport" );
485            boolean globalReport = Boolean.valueOf( ( attr == null ) ? "true" : attr ).booleanValue();
486            attr = element.getAttribute( "groupReport" );
487            boolean groupReport = Boolean.valueOf( ( attr == null ) ? "true" : attr ).booleanValue();
488            Map params = parseParams( DOMUtils.getChildElementsByTagName( element, "param" ) );
489            return new LogDistiller.Plugin( type, globalReport, groupReport, params );
490        }
491    }