View Javadoc
1   package net.sf.logdistiller.xml;
2   
3   /*
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  import java.io.*;
18  import java.util.*;
19  
20  import javax.xml.parsers.DocumentBuilder;
21  import javax.xml.parsers.DocumentBuilderFactory;
22  import javax.xml.parsers.ParserConfigurationException;
23  import org.w3c.dom.Document;
24  import org.w3c.dom.Element;
25  import org.xml.sax.InputSource;
26  import org.xml.sax.SAXException;
27  
28  import net.sf.logdistiller.*;
29  import net.sf.logdistiller.util.PropertiesReplacer;
30  import org.xml.sax.ErrorHandler;
31  
32  /**
33   * This class parses an XML rules configuration file and creates corresponding LogDistiller object.
34   *
35   * @see net.sf.logdistiller.LogDistiller
36   */
37  public class DOMConfigurator
38  {
39      private final Map<String, String> overriddenProperties;
40  
41      private PropertiesReplacer replacer = null;
42  
43      private boolean workInProgress = false;
44  
45      private boolean old;
46  
47      private Set<String> warnings = null;
48  
49      private LogDistiller ld = null;
50  
51      public DOMConfigurator()
52      {
53          overriddenProperties = null;
54      }
55  
56      public DOMConfigurator( Map<String, String> overriddenProperties )
57      {
58          this.overriddenProperties = overriddenProperties;
59      }
60  
61      synchronized public LogDistiller read( File f )
62          throws ParserConfigurationException, IOException, SAXException
63      {
64          return read( f, null );
65      }
66  
67      synchronized public LogDistiller read( File f, ErrorHandler errorHandler )
68          throws ParserConfigurationException, IOException, SAXException
69      {
70          return read( new FileInputStream( f ), errorHandler );
71      }
72  
73      synchronized public LogDistiller read( InputStream in )
74          throws ParserConfigurationException, IOException, SAXException
75      {
76          return read( in, null );
77      }
78  
79      synchronized public LogDistiller read( InputStream in, ErrorHandler errorHandler )
80          throws ParserConfigurationException, IOException, SAXException
81      {
82          return read( new InputSource( in ), errorHandler );
83      }
84  
85      synchronized public LogDistiller read( InputSource inputSource )
86          throws ParserConfigurationException, IOException, SAXException
87      {
88          return read( inputSource, null );
89      }
90  
91      /**
92       * begin configuration, if not currently in progress
93       *
94       * @return boolean true if really begin, false if configuration is already in progress
95       */
96      protected boolean begin()
97      {
98          if ( workInProgress )
99          {
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 }