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 8810 2012-12-04 00:45:37Z 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" target="alexandria_uri">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     /** 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 }