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