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