001/* 002 * jDTAUS Banking RI Textschluesselverzeichnis 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.banking.ri.txtdirectory; 022 023import java.io.File; 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.URI; 027import java.net.URISyntaxException; 028import java.net.URL; 029import java.text.DateFormat; 030import java.text.ParseException; 031import java.text.SimpleDateFormat; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Calendar; 035import java.util.Collection; 036import java.util.Date; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.Iterator; 040import java.util.LinkedList; 041import java.util.List; 042import java.util.Locale; 043import java.util.Map; 044import javax.xml.parsers.DocumentBuilder; 045import javax.xml.parsers.DocumentBuilderFactory; 046import javax.xml.parsers.ParserConfigurationException; 047import org.jdtaus.banking.Textschluessel; 048import org.jdtaus.banking.TextschluesselVerzeichnis; 049import org.jdtaus.banking.messages.ReadsTextschluesselMessage; 050import org.jdtaus.banking.messages.SearchesTextschluesselMessage; 051import org.jdtaus.core.container.ContainerFactory; 052import org.jdtaus.core.container.PropertyException; 053import org.jdtaus.core.logging.spi.Logger; 054import org.jdtaus.core.monitor.spi.Task; 055import org.jdtaus.core.monitor.spi.TaskMonitor; 056import org.jdtaus.core.sax.util.EntityResolverChain; 057import org.w3c.dom.Document; 058import org.w3c.dom.Element; 059import org.w3c.dom.NodeList; 060import org.xml.sax.ErrorHandler; 061import org.xml.sax.SAXException; 062import org.xml.sax.SAXParseException; 063 064/** 065 * Textschlüssel directory implementation backed by XML files. 066 * <p>This implementation uses XML resources provided by any available {@link JaxpTextschluesselProvider} 067 * implementation. Resources with a {@code file} URI scheme are monitored for changes by querying the last modification 068 * time. Monitoring is controlled by property {@code reloadIntervalMillis}.</p> 069 * 070 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 071 * @version $JDTAUS: JaxpTextschluesselVerzeichnis.java 8865 2014-01-10 17:13:42Z schulte $ 072 */ 073public class JaxpTextschluesselVerzeichnis implements TextschluesselVerzeichnis 074{ 075 076 /** JAXP configuration key to the Schema implementation attribute. */ 077 private static final String SCHEMA_LANGUAGE_KEY = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; 078 079 /** 080 * JAXP Schema implementation to use. 081 * @see javax.xml.XMLConstants#W3C_XML_SCHEMA_NS_URI 082 */ 083 private static final String SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema"; 084 085 /** jDTAUS {@code textschluessel} namespace URI. */ 086 private static final String TEXTSCHLUESSEL_NS = "http://jdtaus.org/banking/xml/textschluessel"; 087 088 /** jDTAUS {@code banking} namespace URI. */ 089 private static final String BANKING_NS = "http://jdtaus.org/banking/model"; 090 091 /** {@code http://www.w3.org/2001/XMLSchema-instance} namespace URI. */ 092 private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"; 093 094 /** Version supported by this implementation. */ 095 private static final String[] SUPPORTED_VERSIONS = 096 { 097 "1.0", "1.1" 098 }; 099 100 /* Flag indicating that initialization has been performed. */ 101 private boolean initialized; 102 103 /** Holds the loaded Textschlüssel instances. */ 104 private Textschluessel[] instances; 105 106 /** 107 * Holds mappings of Textschlüssel keys to instances. 108 * @since 1.15 109 */ 110 private final Map textschluesselMap = new HashMap(); 111 112 /** 113 * Holds mappings of Textschlüssel keys to instances with variable extension. 114 * @since 1.15 115 */ 116 private final Map variableTextschluesselMap = new HashMap(); 117 118 /** Maps {@code File} instances to theire last modification timestamp. */ 119 private final Map monitorMap = new HashMap(); 120 121 /** Holds the timestamp resources got checked for modifications. */ 122 private long lastCheck = System.currentTimeMillis(); 123 124 /** Number of milliseconds to pass before resources are checked for modifications. */ 125 private Long reloadIntervalMillis; 126 127 /** Number of Textschluessel for which progress monitoring gets enabled. */ 128 private Long monitoringThreshold; 129 130 /** 131 * Creates a new {@code XMLTextschluesselVerzeichnis} instance taking the number of milliseconds to pass before 132 * resources are checked for modifications and the number of Textschluessel for which progress monitoring gets 133 * enabled. 134 * 135 * @param reloadIntervalMillis Number of milliseconds to pass before resources are checked for modifications. 136 * @param monitoringThreshold Number of Textschluessel for which progress monitoring gets enabled. 137 */ 138 public JaxpTextschluesselVerzeichnis( final long reloadIntervalMillis, final long monitoringThreshold ) 139 { 140 this(); 141 if ( reloadIntervalMillis > 0 ) 142 { 143 this.reloadIntervalMillis = new Long( reloadIntervalMillis ); 144 } 145 if ( monitoringThreshold > 0 ) 146 { 147 this.monitoringThreshold = new Long( monitoringThreshold ); 148 } 149 } 150 151 /** 152 * Gets the number of milliseconds to pass before resources are checked for modifications. 153 * 154 * @return The number of milliseconds to pass before resources are checked for modifications. 155 */ 156 public long getReloadIntervalMillis() 157 { 158 if ( this.reloadIntervalMillis == null ) 159 { 160 this.reloadIntervalMillis = this.getDefaultReloadIntervalMillis(); 161 } 162 163 return this.reloadIntervalMillis.longValue(); 164 } 165 166 /** 167 * Gets the number of Textschluessel for which progress monitoring gets enabled. 168 * 169 * @return The number of Textschluessel for which progress monitoring gets enabled. 170 */ 171 public long getMonitoringThreshold() 172 { 173 if ( this.monitoringThreshold == null ) 174 { 175 this.monitoringThreshold = this.getDefaultMonitoringThreshold(); 176 } 177 178 return this.monitoringThreshold.longValue(); 179 } 180 181 public Textschluessel[] getTextschluessel() 182 { 183 this.assertValidProperties(); 184 this.assertInitialized(); 185 return this.searchTextschluessel( null, null, null ); 186 } 187 188 public Textschluessel getTextschluessel( final int key, final int extension ) 189 { 190 if ( key < 0 || key > 99 ) 191 { 192 throw new IllegalArgumentException( Integer.toString( key ) ); 193 } 194 if ( extension < 0 || extension > 999 ) 195 { 196 throw new IllegalArgumentException( Integer.toString( extension ) ); 197 } 198 199 this.assertValidProperties(); 200 this.assertInitialized(); 201 202 Textschluessel textschluessel = null; 203 final Number keyObject = new Integer( key ); 204 final Map extensionMap = (Map) this.textschluesselMap.get( keyObject ); 205 206 if ( extensionMap != null ) 207 { 208 textschluessel = (Textschluessel) extensionMap.get( new Integer( extension ) ); 209 210 if ( textschluessel != null ) 211 { 212 textschluessel = (Textschluessel) textschluessel.clone(); 213 } 214 } 215 216 if ( textschluessel == null ) 217 { 218 textschluessel = (Textschluessel) this.variableTextschluesselMap.get( keyObject ); 219 220 if ( textschluessel != null ) 221 { 222 textschluessel = (Textschluessel) textschluessel.clone(); 223 textschluessel.setExtension( extension ); 224 } 225 } 226 227 return textschluessel; 228 } 229 230 public Textschluessel getTextschluessel( final int key, final int extension, final Date date ) 231 { 232 if ( key < 0 || key > 99 ) 233 { 234 throw new IllegalArgumentException( Integer.toString( key ) ); 235 } 236 if ( extension < 0 || extension > 999 ) 237 { 238 throw new IllegalArgumentException( Integer.toString( extension ) ); 239 } 240 if ( date == null ) 241 { 242 throw new NullPointerException( "date" ); 243 } 244 245 this.assertValidProperties(); 246 this.assertInitialized(); 247 248 final Textschluessel textschluessel = this.getTextschluessel( key, extension ); 249 return textschluessel != null && textschluessel.isValidAt( date ) ? textschluessel : null; 250 } 251 252 public final Textschluessel[] search( final boolean debit, final boolean remittance ) 253 { 254 return this.searchTextschluessel( Boolean.valueOf( debit ), Boolean.valueOf( remittance ), null ); 255 } 256 257 public final Textschluessel[] search( final boolean debit, final boolean remittance, final Date date ) 258 { 259 if ( date == null ) 260 { 261 throw new NullPointerException( "date" ); 262 } 263 264 return this.searchTextschluessel( Boolean.valueOf( debit ), Boolean.valueOf( remittance ), date ); 265 } 266 267 public Textschluessel[] searchTextschluessel( final Boolean debit, final Boolean remittance, final Date date ) 268 { 269 this.assertValidProperties(); 270 this.assertInitialized(); 271 272 final Collection col = new ArrayList( this.instances.length ); 273 274 if ( this.instances.length > 0 ) 275 { 276 final Task task = new Task(); 277 task.setCancelable( true ); 278 task.setDescription( new SearchesTextschluesselMessage() ); 279 task.setIndeterminate( false ); 280 task.setMaximum( this.instances.length - 1 ); 281 task.setMinimum( 0 ); 282 task.setProgress( 0 ); 283 284 try 285 { 286 if ( task.getMaximum() > this.getMonitoringThreshold() ) 287 { 288 this.getTaskMonitor().monitor( task ); 289 } 290 291 for ( int i = this.instances.length - 1; i >= 0 && !task.isCancelled(); i-- ) 292 { 293 task.setProgress( task.getMaximum() - i ); 294 295 if ( ( debit == null ? true : this.instances[i].isDebit() == debit.booleanValue() ) 296 && ( remittance == null ? true : this.instances[i].isRemittance() == remittance.booleanValue() ) 297 && ( date == null ? true : this.instances[i].isValidAt( date ) ) ) 298 { 299 col.add( this.instances[i].clone() ); 300 } 301 } 302 303 if ( task.isCancelled() ) 304 { 305 col.clear(); 306 } 307 308 } 309 finally 310 { 311 if ( task.getMaximum() > this.getMonitoringThreshold() ) 312 { 313 this.getTaskMonitor().finish( task ); 314 } 315 } 316 } 317 318 return (Textschluessel[]) col.toArray( new Textschluessel[ col.size() ] ); 319 } 320 321 /** 322 * Initializes the instance to hold the parsed XML Textschluessel instances. 323 * 324 * @see #assertValidProperties() 325 * @see #parseResources() 326 * @see #transformDocument(Document) 327 */ 328 private synchronized void assertInitialized() 329 { 330 try 331 { 332 if ( System.currentTimeMillis() - this.lastCheck > this.getReloadIntervalMillis() 333 && !this.monitorMap.isEmpty() ) 334 { 335 this.lastCheck = System.currentTimeMillis(); 336 for ( final Iterator it = this.monitorMap.entrySet().iterator(); it.hasNext(); ) 337 { 338 final Map.Entry entry = (Map.Entry) it.next(); 339 final File file = (File) entry.getKey(); 340 final Long lastModified = (Long) entry.getValue(); 341 342 assert lastModified != null : "Expected modification time."; 343 344 if ( file.lastModified() != lastModified.longValue() ) 345 { 346 this.getLogger().info( this.getChangeInfoMessage( this.getLocale(), file.getAbsolutePath() ) ); 347 this.initialized = false; 348 break; 349 } 350 } 351 } 352 353 if ( !this.initialized ) 354 { 355 this.monitorMap.clear(); 356 this.textschluesselMap.clear(); 357 this.variableTextschluesselMap.clear(); 358 359 final List/*<Document>*/ documents = this.parseResources(); 360 final Collection parsedTextschluessel = new LinkedList(); 361 362 for ( final Iterator it = documents.iterator(); it.hasNext(); ) 363 { 364 final Document document = (Document) it.next(); 365 parsedTextschluessel.addAll( this.transformDocument( document ) ); 366 } 367 368 final Collection checked = new ArrayList( parsedTextschluessel.size() ); 369 370 for ( final Iterator it = parsedTextschluessel.iterator(); it.hasNext(); ) 371 { 372 final Textschluessel i = (Textschluessel) it.next(); 373 final Integer key = new Integer( i.getKey() ); 374 final Integer ext = new Integer( i.getExtension() ); 375 376 if ( i.isVariable() ) 377 { 378 if ( this.textschluesselMap.containsKey( key ) 379 || this.variableTextschluesselMap.put( key, i ) != null ) 380 { 381 throw new IllegalStateException( this.getDuplicateTextschluesselMessage( 382 this.getLocale(), key, ext ) ); 383 384 } 385 } 386 else 387 { 388 Map extensionsMap = (Map) this.textschluesselMap.get( key ); 389 390 if ( extensionsMap == null ) 391 { 392 extensionsMap = new HashMap(); 393 this.textschluesselMap.put( key, extensionsMap ); 394 } 395 396 if ( extensionsMap.put( ext, i ) != null ) 397 { 398 throw new IllegalStateException( this.getDuplicateTextschluesselMessage( 399 this.getLocale(), key, ext ) ); 400 401 } 402 } 403 404 checked.add( i ); 405 } 406 407 this.instances = (Textschluessel[]) checked.toArray( new Textschluessel[ checked.size() ] ); 408 this.getLogger().info( this.getTextschluesselInfoMessage( 409 this.getLocale(), new Integer( this.instances.length ), new Integer( documents.size() ) ) ); 410 411 this.initialized = true; 412 } 413 } 414 catch ( final IOException e ) 415 { 416 this.initialized = false; 417 throw new RuntimeException( e ); 418 } 419 catch ( final SAXException e ) 420 { 421 this.initialized = false; 422 throw new RuntimeException( e ); 423 } 424 catch ( final ParserConfigurationException e ) 425 { 426 this.initialized = false; 427 throw new RuntimeException( e ); 428 } 429 catch ( final ParseException e ) 430 { 431 this.initialized = false; 432 throw new RuntimeException( e ); 433 } 434 } 435 436 /** 437 * Checks configured properties. 438 * 439 * @throws PropertyException if properties hold invalid values. 440 */ 441 private void assertValidProperties() 442 { 443 if ( this.getReloadIntervalMillis() < 0L ) 444 { 445 throw new PropertyException( "reloadIntervalMillis", Long.toString( this.getReloadIntervalMillis() ) ); 446 } 447 if ( this.getMonitoringThreshold() < 0L ) 448 { 449 throw new PropertyException( "monitoringThreshold", Long.toString( this.getMonitoringThreshold() ) ); 450 } 451 } 452 453 /** 454 * Gets XML resources provided by any available {@code TextschluesselProvider} implementation. 455 * 456 * @return XML resources provided by any available {@code TextschluesselProvider} implementation. 457 * 458 * @throws IOException if retrieving the resources fails. 459 * 460 * @see JaxpTextschluesselProvider 461 */ 462 private URL[] getResources() throws IOException 463 { 464 final Collection resources = new HashSet(); 465 final JaxpTextschluesselProvider[] provider = this.getTextschluesselProvider(); 466 467 for ( int i = provider.length - 1; i >= 0; i-- ) 468 { 469 resources.addAll( Arrays.asList( provider[i].getResources() ) ); 470 } 471 472 return (URL[]) resources.toArray( new URL[ resources.size() ] ); 473 } 474 475 /** 476 * Adds a resource to the list of resources to monitor for changes. 477 * 478 * @param url the URL of the resource to monitor for changes. 479 * 480 * @throws NullPointerException if {@code url} is {@code null}. 481 */ 482 private void monitorResource( final URL url ) 483 { 484 if ( url == null ) 485 { 486 throw new NullPointerException( "url" ); 487 } 488 489 try 490 { 491 final File file = new File( new URI( url.toString() ) ); 492 this.monitorMap.put( file, new Long( file.lastModified() ) ); 493 this.getLogger().info( this.getMonitoringInfoMessage( this.getLocale(), file.getAbsolutePath() ) ); 494 } 495 catch ( final IllegalArgumentException e ) 496 { 497 this.getLogger().info( this.getNotMonitoringWarningMessage( 498 this.getLocale(), url.toExternalForm(), e.getMessage() ) ); 499 500 } 501 catch ( final URISyntaxException e ) 502 { 503 this.getLogger().info( this.getNotMonitoringWarningMessage( 504 this.getLocale(), url.toExternalForm(), e.getMessage() ) ); 505 506 } 507 } 508 509 /** 510 * Parses all XML resources. 511 * 512 * @return the parsed XML documents. 513 * 514 * @see #getResources() 515 * @see #getDocumentBuilder() 516 * 517 * @throws ParserConfigurationException if configuring the parser fails. 518 * @throws IOException if reading resources fails. 519 * @throws SAXException if parsing fails. 520 */ 521 private List/*<Document>*/ parseResources() throws ParserConfigurationException, IOException, SAXException 522 { 523 InputStream stream = null; 524 525 final URL[] resources = this.getResources(); 526 final List documents = new LinkedList(); 527 528 if ( resources.length > 0 ) 529 { 530 final DocumentBuilder validatingParser = this.getDocumentBuilder(); 531 final DocumentBuilderFactory namespaceAwareFactory = DocumentBuilderFactory.newInstance(); 532 533 namespaceAwareFactory.setNamespaceAware( true ); 534 final DocumentBuilder nonValidatingParser = namespaceAwareFactory.newDocumentBuilder(); 535 536 final Task task = new Task(); 537 task.setCancelable( false ); 538 task.setDescription( new ReadsTextschluesselMessage() ); 539 task.setIndeterminate( false ); 540 task.setMaximum( resources.length - 1 ); 541 task.setMinimum( 0 ); 542 task.setProgress( 0 ); 543 544 try 545 { 546 this.getTaskMonitor().monitor( task ); 547 548 for ( int i = resources.length - 1; i >= 0; i-- ) 549 { 550 task.setProgress( task.getMaximum() - i ); 551 final URL resource = resources[i]; 552 final ErrorHandler errorHandler = new ErrorHandler() 553 { 554 555 public void warning( final SAXParseException e ) 556 throws SAXException 557 { 558 getLogger().warn( getParseExceptionMessage( 559 getLocale(), resource.toExternalForm(), 560 e.getMessage(), new Integer( e.getLineNumber() ), 561 new Integer( e.getColumnNumber() ) ) ); 562 563 } 564 565 public void error( final SAXParseException e ) 566 throws SAXException 567 { 568 throw new SAXException( getParseExceptionMessage( 569 getLocale(), resource.toExternalForm(), 570 e.getMessage(), new Integer( e.getLineNumber() ), 571 new Integer( e.getColumnNumber() ) ), e ); 572 573 } 574 575 public void fatalError( final SAXParseException e ) 576 throws SAXException 577 { 578 throw new SAXException( getParseExceptionMessage( 579 getLocale(), resource.toExternalForm(), 580 e.getMessage(), new Integer( e.getLineNumber() ), 581 new Integer( e.getColumnNumber() ) ), e ); 582 583 } 584 585 }; 586 587 nonValidatingParser.setErrorHandler( errorHandler ); 588 validatingParser.setErrorHandler( errorHandler ); 589 590 this.monitorResource( resource ); 591 stream = resource.openStream(); 592 Document doc = nonValidatingParser.parse( stream ); 593 if ( doc.getDocumentElement().hasAttributeNS( XSI_NS, "schemaLocation" ) ) 594 { 595 stream.close(); 596 stream = resource.openStream(); 597 doc = validatingParser.parse( stream ); 598 } 599 else if ( this.getLogger().isInfoEnabled() ) 600 { 601 this.getLogger().info( 602 this.getNoSchemaLocationMessage( this.getLocale(), resource.toExternalForm() ) ); 603 } 604 605 documents.add( doc ); 606 stream.close(); 607 } 608 } 609 finally 610 { 611 this.getTaskMonitor().finish( task ); 612 } 613 } 614 else 615 { 616 this.getLogger().warn( this.getNoTextschluesselFoundMessage( this.getLocale() ) ); 617 } 618 619 return documents; 620 } 621 622 /** 623 * Transforms a document to the Textschluessel instances it contains. 624 * 625 * @param doc the document to transform. 626 * 627 * @return an array of Textschluessel instances from the given document. 628 * 629 * @throws IllegalArgumentException if {@code doc} cannot be transformed. 630 * @throws ParseException if parsing fails. 631 * 632 * @see #transformTextschluesselDocument(Document) 633 * @see #transformBankingDocument(Document) 634 */ 635 private List/*<Textschluessel>*/ transformDocument( final Document doc ) throws ParseException 636 { 637 String modelVersion = null; 638 final String namespace = doc.getDocumentElement().getNamespaceURI(); 639 640 if ( namespace == null ) 641 { 642 throw new RuntimeException( this.getUnsupportedNamespaceMessage( this.getLocale(), null ) ); 643 } 644 else if ( TEXTSCHLUESSEL_NS.equals( namespace ) ) 645 { 646 modelVersion = doc.getDocumentElement().getAttributeNS( namespace, "version" ); 647 } 648 else if ( BANKING_NS.equals( namespace ) ) 649 { 650 modelVersion = doc.getDocumentElement().getAttributeNS( namespace, "modelVersion" ); 651 } 652 else 653 { 654 throw new RuntimeException( this.getUnsupportedNamespaceMessage( this.getLocale(), namespace ) ); 655 } 656 657 boolean supportedModelVersion = false; 658 for ( int i = SUPPORTED_VERSIONS.length - 1; i >= 0; i-- ) 659 { 660 if ( SUPPORTED_VERSIONS[i].equals( modelVersion ) ) 661 { 662 supportedModelVersion = true; 663 break; 664 } 665 } 666 667 if ( !supportedModelVersion ) 668 { 669 throw new RuntimeException( this.getUnsupportedModelVersionMessage( this.getLocale(), modelVersion ) ); 670 } 671 672 final List textschluessel = new LinkedList(); 673 674 if ( namespace.equals( TEXTSCHLUESSEL_NS ) ) 675 { 676 textschluessel.addAll( this.transformTextschluesselDocument( doc ) ); 677 } 678 else if ( namespace.equals( BANKING_NS ) ) 679 { 680 textschluessel.addAll( this.transformBankingDocument( doc ) ); 681 } 682 683 return textschluessel; 684 } 685 686 /** 687 * Transforms a document from deprecated {@code textschluessel} namespace to the {@code Textschluessel} instances it 688 * contains. 689 * 690 * @param doc the document to transform. 691 * 692 * @return an list of Textschluessel instances from the given document. 693 * 694 * @throws IllegalArgumentException if {@code doc} contains invalid content. 695 */ 696 private List/*<Textschluessel>*/ transformTextschluesselDocument( final Document doc ) 697 { 698 final List list = new LinkedList(); 699 final NodeList typeList = doc.getDocumentElement().getElementsByTagNameNS( 700 TEXTSCHLUESSEL_NS, "transactionTypes" ); 701 702 for ( int i = typeList.getLength() - 1; i >= 0; i-- ) 703 { 704 final Element parent = (Element) typeList.item( i ); 705 if ( parent.getParentNode().equals( doc.getDocumentElement() ) ) 706 { 707 final NodeList type = parent.getElementsByTagNameNS( TEXTSCHLUESSEL_NS, "transactionType" ); 708 for ( int t = type.getLength() - 1; t >= 0; t-- ) 709 { 710 final Element e = (Element) type.item( t ); 711 if ( e.getParentNode().equals( parent ) ) 712 { 713 final Textschluessel textschluessel = new Textschluessel(); 714 list.add( textschluessel ); 715 716 final String textschluesselType = e.getAttributeNS( TEXTSCHLUESSEL_NS, "type" ); 717 textschluessel.setDebit( "DEBIT".equals( textschluesselType ) ); 718 textschluessel.setRemittance( "REMITTANCE".equals( textschluesselType ) ); 719 textschluessel.setKey( Integer.valueOf( 720 e.getAttributeNS( TEXTSCHLUESSEL_NS, "key" ) ).intValue() ); 721 722 final String extension = e.getAttributeNS( TEXTSCHLUESSEL_NS, "extension" ); 723 if ( "VARIABLE".equals( extension ) ) 724 { 725 textschluessel.setVariable( true ); 726 textschluessel.setExtension( 0 ); 727 } 728 else 729 { 730 textschluessel.setExtension( Integer.valueOf( extension ).intValue() ); 731 } 732 733 final NodeList descriptions = e.getElementsByTagNameNS( TEXTSCHLUESSEL_NS, "description" ); 734 for ( int d = descriptions.getLength() - 1; d >= 0; d-- ) 735 { 736 final Element description = (Element) descriptions.item( d ); 737 738 if ( description.getParentNode().equals( e ) ) 739 { 740 final String language = description.getAttributeNS( TEXTSCHLUESSEL_NS, "language" ); 741 final String text = description.getFirstChild().getNodeValue(); 742 textschluessel.setShortDescription( new Locale( language.toLowerCase() ), text ); 743 } 744 } 745 } 746 } 747 } 748 } 749 750 return list; 751 } 752 753 /** 754 * Transforms a document from the {@code banking} namespace to the {@code Textschluessel} instances it contains. 755 * 756 * @param doc the document to transform. 757 * 758 * @return an list of Textschluessel instances from the given document. 759 * 760 * @throws IllegalArgumentException if {@code doc} contains invalid content. 761 * @throws ParseException if parsing fails. 762 */ 763 private List/*<Textschluessel>*/ transformBankingDocument( final Document doc ) throws ParseException 764 { 765 final List list = new LinkedList(); 766 final Calendar cal = Calendar.getInstance(); 767 final DateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" ); 768 final String systemLanguage = Locale.getDefault().getLanguage().toLowerCase(); 769 770 final NodeList typeList = doc.getDocumentElement().getElementsByTagNameNS( BANKING_NS, "textschluessel" ); 771 for ( int i = typeList.getLength() - 1; i >= 0; i-- ) 772 { 773 final Element e = (Element) typeList.item( i ); 774 if ( e.getParentNode().equals( doc.getDocumentElement() ) ) 775 { 776 final Textschluessel textschluessel = new Textschluessel(); 777 list.add( textschluessel ); 778 779 textschluessel.setKey( Integer.valueOf( e.getAttributeNS( BANKING_NS, "key" ) ).intValue() ); 780 if ( e.hasAttributeNS( BANKING_NS, "extension" ) ) 781 { 782 textschluessel.setExtension( Integer.valueOf( 783 e.getAttributeNS( BANKING_NS, "extension" ) ).intValue() ); 784 785 } 786 787 textschluessel.setDebit( Boolean.valueOf( e.getAttributeNS( BANKING_NS, "debit" ) ).booleanValue() ); 788 textschluessel.setRemittance( Boolean.valueOf( 789 e.getAttributeNS( BANKING_NS, "remittance" ) ).booleanValue() ); 790 791 textschluessel.setVariable( Boolean.valueOf( 792 e.getAttributeNS( BANKING_NS, "variableExtension" ) ).booleanValue() ); 793 794 final NodeList texts = e.getElementsByTagNameNS( BANKING_NS, "texts" ); 795 if ( e.hasAttributeNS( BANKING_NS, "validFrom" ) ) 796 { 797 cal.setTime( dateFormat.parse( e.getAttributeNS( BANKING_NS, "validFrom" ) ) ); 798 cal.set( Calendar.HOUR_OF_DAY, 0 ); 799 cal.set( Calendar.MINUTE, 0 ); 800 cal.set( Calendar.SECOND, 0 ); 801 cal.set( Calendar.MILLISECOND, 0 ); 802 textschluessel.setValidFrom( cal.getTime() ); 803 } 804 805 if ( e.hasAttributeNS( BANKING_NS, "validTo" ) ) 806 { 807 cal.setTime( dateFormat.parse( e.getAttributeNS( BANKING_NS, "validTo" ) ) ); 808 cal.set( Calendar.HOUR_OF_DAY, 0 ); 809 cal.set( Calendar.MINUTE, 0 ); 810 cal.set( Calendar.SECOND, 0 ); 811 cal.set( Calendar.MILLISECOND, 0 ); 812 textschluessel.setValidTo( cal.getTime() ); 813 } 814 815 for ( int t = texts.getLength() - 1; t >= 0; t-- ) 816 { 817 final Element textsElement = (Element) texts.item( t ); 818 if ( textsElement.getParentNode().equals( e ) ) 819 { 820 final String defaultLanguage = 821 textsElement.getAttributeNS( BANKING_NS, "defaultLanguage" ).toLowerCase(); 822 823 boolean hasSystemLanguage = false; 824 String defaultText = null; 825 826 final NodeList l = textsElement.getElementsByTagNameNS( BANKING_NS, "text" ); 827 828 for ( int d = l.getLength() - 1; d >= 0; d-- ) 829 { 830 final Element description = (Element) l.item( d ); 831 if ( description.getParentNode().equals( textsElement ) ) 832 { 833 final String language = description.getAttributeNS( 834 BANKING_NS, "language" ).toLowerCase(); 835 836 final String text = description.getFirstChild().getNodeValue(); 837 838 if ( language.equals( defaultLanguage ) ) 839 { 840 defaultText = text; 841 } 842 843 if ( systemLanguage.equals( language ) ) 844 { 845 hasSystemLanguage = true; 846 } 847 848 textschluessel.setShortDescription( new Locale( language ), text ); 849 } 850 } 851 852 if ( !hasSystemLanguage ) 853 { 854 textschluessel.setShortDescription( new Locale( systemLanguage ), defaultText ); 855 } 856 } 857 } 858 } 859 } 860 861 return list; 862 } 863 864 /** 865 * Creates a new {@code DocumentBuilder} to use for parsing the XML resources. 866 * <p>This method tries to set the following JAXP property on the system's default XML parser: 867 * <ul> 868 * <li>{@code http://java.sun.com/xml/jaxp/properties/schemaLanguage} set to 869 * {@code http://www.w3.org/2001/XMLSchema}</li> 870 * </ul>When setting this property fails, a non-validating {@code DocumentBuilder} is returned and a warning message 871 * is logged.</p> 872 * 873 * @return a new {@code DocumentBuilder} to be used for parsing resources. 874 * 875 * @throws ParserConfigurationException if configuring the XML parser fails. 876 */ 877 private DocumentBuilder getDocumentBuilder() throws ParserConfigurationException 878 { 879 final DocumentBuilder xmlBuilder; 880 final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance(); 881 xmlFactory.setNamespaceAware( true ); 882 883 try 884 { 885 xmlFactory.setValidating( true ); 886 xmlFactory.setAttribute( SCHEMA_LANGUAGE_KEY, SCHEMA_LANGUAGE ); 887 } 888 catch ( IllegalArgumentException e ) 889 { 890 this.getLogger().info( this.getNoJAXPValidationWarningMessage( this.getLocale(), e.getMessage() ) ); 891 xmlFactory.setValidating( false ); 892 } 893 894 xmlBuilder = xmlFactory.newDocumentBuilder(); 895 xmlBuilder.setEntityResolver( new EntityResolverChain() ); 896 return xmlBuilder; 897 } 898 899 //--Constructors------------------------------------------------------------ 900 901// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors 902 // This section is managed by jdtaus-container-mojo. 903 904 /** Standard implementation constructor <code>org.jdtaus.banking.ri.txtdirectory.JaxpTextschluesselVerzeichnis</code>. */ 905 public JaxpTextschluesselVerzeichnis() 906 { 907 super(); 908 } 909 910// </editor-fold>//GEN-END:jdtausConstructors 911 912 //------------------------------------------------------------Constructors-- 913 //--Dependencies------------------------------------------------------------ 914 915// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 916 // This section is managed by jdtaus-container-mojo. 917 918 /** 919 * Gets the configured <code>Logger</code> implementation. 920 * 921 * @return The configured <code>Logger</code> implementation. 922 */ 923 private Logger getLogger() 924 { 925 return (Logger) ContainerFactory.getContainer(). 926 getDependency( this, "Logger" ); 927 928 } 929 930 /** 931 * Gets the configured <code>TextschluesselProvider</code> implementation. 932 * 933 * @return The configured <code>TextschluesselProvider</code> implementation. 934 */ 935 private JaxpTextschluesselProvider[] getTextschluesselProvider() 936 { 937 return (JaxpTextschluesselProvider[]) ContainerFactory.getContainer(). 938 getDependency( this, "TextschluesselProvider" ); 939 940 } 941 942 /** 943 * Gets the configured <code>TaskMonitor</code> implementation. 944 * 945 * @return The configured <code>TaskMonitor</code> implementation. 946 */ 947 private TaskMonitor getTaskMonitor() 948 { 949 return (TaskMonitor) ContainerFactory.getContainer(). 950 getDependency( this, "TaskMonitor" ); 951 952 } 953 954 /** 955 * Gets the configured <code>Locale</code> implementation. 956 * 957 * @return The configured <code>Locale</code> implementation. 958 */ 959 private Locale getLocale() 960 { 961 return (Locale) ContainerFactory.getContainer(). 962 getDependency( this, "Locale" ); 963 964 } 965 966// </editor-fold>//GEN-END:jdtausDependencies 967 968 //------------------------------------------------------------Dependencies-- 969 //--Properties-------------------------------------------------------------- 970 971// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 972 // This section is managed by jdtaus-container-mojo. 973 974 /** 975 * Gets the value of property <code>defaultReloadIntervalMillis</code>. 976 * 977 * @return Default number of milliseconds to pass before resources are checked for modifications. 978 */ 979 private java.lang.Long getDefaultReloadIntervalMillis() 980 { 981 return (java.lang.Long) ContainerFactory.getContainer(). 982 getProperty( this, "defaultReloadIntervalMillis" ); 983 984 } 985 986 /** 987 * Gets the value of property <code>defaultMonitoringThreshold</code>. 988 * 989 * @return Default number of Textschlüssel for which progress monitoring gets enabled. 990 */ 991 private java.lang.Long getDefaultMonitoringThreshold() 992 { 993 return (java.lang.Long) ContainerFactory.getContainer(). 994 getProperty( this, "defaultMonitoringThreshold" ); 995 996 } 997 998// </editor-fold>//GEN-END:jdtausProperties 999 1000 //--------------------------------------------------------------Properties-- 1001 //--Messages---------------------------------------------------------------- 1002 1003// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 1004 // This section is managed by jdtaus-container-mojo. 1005 1006 /** 1007 * Gets the text of message <code>noJAXPValidationWarning</code>. 1008 * <blockquote><pre>Keine JAXP Validierung verfügbar. {0}</pre></blockquote> 1009 * <blockquote><pre>No JAXP validation available. {0}</pre></blockquote> 1010 * 1011 * @param locale The locale of the message instance to return. 1012 * @param detailMessage format parameter. 1013 * 1014 * @return the text of message <code>noJAXPValidationWarning</code>. 1015 */ 1016 private String getNoJAXPValidationWarningMessage( final Locale locale, 1017 final java.lang.String detailMessage ) 1018 { 1019 return ContainerFactory.getContainer(). 1020 getMessage( this, "noJAXPValidationWarning", locale, 1021 new Object[] 1022 { 1023 detailMessage 1024 }); 1025 1026 } 1027 1028 /** 1029 * Gets the text of message <code>notMonitoringWarning</code>. 1030 * <blockquote><pre>{0} kann bei Änderung nicht automatisch neu geladen werden. {1}</pre></blockquote> 1031 * <blockquote><pre>{0} cannot be monitored. {1}</pre></blockquote> 1032 * 1033 * @param locale The locale of the message instance to return. 1034 * @param resourceName format parameter. 1035 * @param detailMessage format parameter. 1036 * 1037 * @return the text of message <code>notMonitoringWarning</code>. 1038 */ 1039 private String getNotMonitoringWarningMessage( final Locale locale, 1040 final java.lang.String resourceName, 1041 final java.lang.String detailMessage ) 1042 { 1043 return ContainerFactory.getContainer(). 1044 getMessage( this, "notMonitoringWarning", locale, 1045 new Object[] 1046 { 1047 resourceName, 1048 detailMessage 1049 }); 1050 1051 } 1052 1053 /** 1054 * Gets the text of message <code>changeInfo</code>. 1055 * <blockquote><pre>{0} aktualisiert.</pre></blockquote> 1056 * <blockquote><pre>{0} changed.</pre></blockquote> 1057 * 1058 * @param locale The locale of the message instance to return. 1059 * @param resourceName format parameter. 1060 * 1061 * @return the text of message <code>changeInfo</code>. 1062 */ 1063 private String getChangeInfoMessage( final Locale locale, 1064 final java.lang.String resourceName ) 1065 { 1066 return ContainerFactory.getContainer(). 1067 getMessage( this, "changeInfo", locale, 1068 new Object[] 1069 { 1070 resourceName 1071 }); 1072 1073 } 1074 1075 /** 1076 * Gets the text of message <code>monitoringInfo</code>. 1077 * <blockquote><pre>{0} wird bei Änderung automatisch neu geladen.</pre></blockquote> 1078 * <blockquote><pre>Monitoring {0} for changes.</pre></blockquote> 1079 * 1080 * @param locale The locale of the message instance to return. 1081 * @param resourceName format parameter. 1082 * 1083 * @return the text of message <code>monitoringInfo</code>. 1084 */ 1085 private String getMonitoringInfoMessage( final Locale locale, 1086 final java.lang.String resourceName ) 1087 { 1088 return ContainerFactory.getContainer(). 1089 getMessage( this, "monitoringInfo", locale, 1090 new Object[] 1091 { 1092 resourceName 1093 }); 1094 1095 } 1096 1097 /** 1098 * Gets the text of message <code>textschluesselInfo</code>. 1099 * <blockquote><pre>{1,choice,0#Kein Dokument|1#Ein Dokument|1<{1} Dokumente} gelesen. {0,choice,0#Keine|1#Einen|1<{0}} Textschlüssel verarbeitet.</pre></blockquote> 1100 * <blockquote><pre>Read {1,choice,0#no document|1#one document|1<{1} documents}. Processed {0,choice,0#no entities|1#one entity|1<{0} entities}.</pre></blockquote> 1101 * 1102 * @param locale The locale of the message instance to return. 1103 * @param entityCount format parameter. 1104 * @param documentCount format parameter. 1105 * 1106 * @return the text of message <code>textschluesselInfo</code>. 1107 */ 1108 private String getTextschluesselInfoMessage( final Locale locale, 1109 final java.lang.Number entityCount, 1110 final java.lang.Number documentCount ) 1111 { 1112 return ContainerFactory.getContainer(). 1113 getMessage( this, "textschluesselInfo", locale, 1114 new Object[] 1115 { 1116 entityCount, 1117 documentCount 1118 }); 1119 1120 } 1121 1122 /** 1123 * Gets the text of message <code>unsupportedNamespace</code>. 1124 * <blockquote><pre>Ungültiger XML-Namensraum {0}.</pre></blockquote> 1125 * <blockquote><pre>Unsupported XML namespace {0}.</pre></blockquote> 1126 * 1127 * @param locale The locale of the message instance to return. 1128 * @param namespace format parameter. 1129 * 1130 * @return the text of message <code>unsupportedNamespace</code>. 1131 */ 1132 private String getUnsupportedNamespaceMessage( final Locale locale, 1133 final java.lang.String namespace ) 1134 { 1135 return ContainerFactory.getContainer(). 1136 getMessage( this, "unsupportedNamespace", locale, 1137 new Object[] 1138 { 1139 namespace 1140 }); 1141 1142 } 1143 1144 /** 1145 * Gets the text of message <code>unsupportedModelVersion</code>. 1146 * <blockquote><pre>Keine Unterstützung für Modellversion {0}.</pre></blockquote> 1147 * <blockquote><pre>Unsupported model version {0}.</pre></blockquote> 1148 * 1149 * @param locale The locale of the message instance to return. 1150 * @param modelVersion format parameter. 1151 * 1152 * @return the text of message <code>unsupportedModelVersion</code>. 1153 */ 1154 private String getUnsupportedModelVersionMessage( final Locale locale, 1155 final java.lang.String modelVersion ) 1156 { 1157 return ContainerFactory.getContainer(). 1158 getMessage( this, "unsupportedModelVersion", locale, 1159 new Object[] 1160 { 1161 modelVersion 1162 }); 1163 1164 } 1165 1166 /** 1167 * Gets the text of message <code>parseException</code>. 1168 * <blockquote><pre>Fehler bei der Verarbeitung der Resource "{0}" in Zeile {2}, Spalte {3}. {1}</pre></blockquote> 1169 * <blockquote><pre>Error parsing resource "{0}" at line {2}, column {3}. {1}</pre></blockquote> 1170 * 1171 * @param locale The locale of the message instance to return. 1172 * @param resourceName format parameter. 1173 * @param cause format parameter. 1174 * @param line format parameter. 1175 * @param column format parameter. 1176 * 1177 * @return the text of message <code>parseException</code>. 1178 */ 1179 private String getParseExceptionMessage( final Locale locale, 1180 final java.lang.String resourceName, 1181 final java.lang.String cause, 1182 final java.lang.Number line, 1183 final java.lang.Number column ) 1184 { 1185 return ContainerFactory.getContainer(). 1186 getMessage( this, "parseException", locale, 1187 new Object[] 1188 { 1189 resourceName, 1190 cause, 1191 line, 1192 column 1193 }); 1194 1195 } 1196 1197 /** 1198 * Gets the text of message <code>noSchemaLocation</code>. 1199 * <blockquote><pre>Kein schemaLocation Attribut in Ressource "{0}". Keine Schema-Validierung.</pre></blockquote> 1200 * <blockquote><pre>No schemaLocation attribute in resource "{0}". Schema validation skipped.</pre></blockquote> 1201 * 1202 * @param locale The locale of the message instance to return. 1203 * @param resource format parameter. 1204 * 1205 * @return the text of message <code>noSchemaLocation</code>. 1206 */ 1207 private String getNoSchemaLocationMessage( final Locale locale, 1208 final java.lang.String resource ) 1209 { 1210 return ContainerFactory.getContainer(). 1211 getMessage( this, "noSchemaLocation", locale, 1212 new Object[] 1213 { 1214 resource 1215 }); 1216 1217 } 1218 1219 /** 1220 * Gets the text of message <code>duplicateTextschluessel</code>. 1221 * <blockquote><pre>Textschlüssel {0,number,00}{1,number,000} ist mehrfach vorhanden.</pre></blockquote> 1222 * <blockquote><pre>Non-unique Textschluessel {0,number,00}{1,number,000}.</pre></blockquote> 1223 * 1224 * @param locale The locale of the message instance to return. 1225 * @param key format parameter. 1226 * @param extension format parameter. 1227 * 1228 * @return the text of message <code>duplicateTextschluessel</code>. 1229 */ 1230 private String getDuplicateTextschluesselMessage( final Locale locale, 1231 final java.lang.Number key, 1232 final java.lang.Number extension ) 1233 { 1234 return ContainerFactory.getContainer(). 1235 getMessage( this, "duplicateTextschluessel", locale, 1236 new Object[] 1237 { 1238 key, 1239 extension 1240 }); 1241 1242 } 1243 1244 /** 1245 * Gets the text of message <code>noTextschluesselFound</code>. 1246 * <blockquote><pre>Keine Textschlüssel gefunden.</pre></blockquote> 1247 * <blockquote><pre>No Textschlüssel found.</pre></blockquote> 1248 * 1249 * @param locale The locale of the message instance to return. 1250 * 1251 * @return the text of message <code>noTextschluesselFound</code>. 1252 */ 1253 private String getNoTextschluesselFoundMessage( final Locale locale ) 1254 { 1255 return ContainerFactory.getContainer(). 1256 getMessage( this, "noTextschluesselFound", locale, null ); 1257 1258 } 1259 1260// </editor-fold>//GEN-END:jdtausMessages 1261 1262 //----------------------------------------------------------------Messages-- 1263}