View Javadoc
1   /*
2    *  jDTAUS Banking RI Textschluesselverzeichnis
3    *  Copyright (C) 2005 Christian Schulte
4    *  <cs@schulte.it>
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19   *
20   */
21  package org.jdtaus.banking.ri.txtdirectory;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.text.DateFormat;
30  import java.text.ParseException;
31  import java.text.SimpleDateFormat;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Calendar;
35  import java.util.Collection;
36  import java.util.Date;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.Iterator;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Locale;
43  import java.util.Map;
44  import javax.xml.parsers.DocumentBuilder;
45  import javax.xml.parsers.DocumentBuilderFactory;
46  import javax.xml.parsers.ParserConfigurationException;
47  import org.jdtaus.banking.Textschluessel;
48  import org.jdtaus.banking.TextschluesselVerzeichnis;
49  import org.jdtaus.banking.messages.ReadsTextschluesselMessage;
50  import org.jdtaus.banking.messages.SearchesTextschluesselMessage;
51  import org.jdtaus.core.container.ContainerFactory;
52  import org.jdtaus.core.container.PropertyException;
53  import org.jdtaus.core.logging.spi.Logger;
54  import org.jdtaus.core.monitor.spi.Task;
55  import org.jdtaus.core.monitor.spi.TaskMonitor;
56  import org.jdtaus.core.sax.util.EntityResolverChain;
57  import org.w3c.dom.Document;
58  import org.w3c.dom.Element;
59  import org.w3c.dom.NodeList;
60  import org.xml.sax.ErrorHandler;
61  import org.xml.sax.SAXException;
62  import org.xml.sax.SAXParseException;
63  
64  /**
65   * Textschlüssel directory implementation backed by XML files.
66   * <p>This implementation uses XML resources provided by any available {@link JaxpTextschluesselProvider}
67   * implementation. Resources with a {@code file} URI scheme are monitored for changes by querying the last modification
68   * time. Monitoring is controlled by property {@code reloadIntervalMillis}.</p>
69   *
70   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
71   * @version $JDTAUS: JaxpTextschluesselVerzeichnis.java 8865 2014-01-10 17:13:42Z schulte $
72   */
73  public class JaxpTextschluesselVerzeichnis implements TextschluesselVerzeichnis
74  {
75  
76      /** JAXP configuration key to the Schema implementation attribute. */
77      private static final String SCHEMA_LANGUAGE_KEY = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
78  
79      /**
80       * JAXP Schema implementation to use.
81       * @see javax.xml.XMLConstants#W3C_XML_SCHEMA_NS_URI
82       */
83      private static final String SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
84  
85      /** jDTAUS {@code textschluessel} namespace URI. */
86      private static final String TEXTSCHLUESSEL_NS = "http://jdtaus.org/banking/xml/textschluessel";
87  
88      /** jDTAUS {@code banking} namespace URI. */
89      private static final String BANKING_NS = "http://jdtaus.org/banking/model";
90  
91      /** {@code http://www.w3.org/2001/XMLSchema-instance} namespace URI. */
92      private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
93  
94      /** Version supported by this implementation. */
95      private static final String[] SUPPORTED_VERSIONS =
96      {
97          "1.0", "1.1"
98      };
99  
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 }