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}