1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.jdtaus.banking.ri.blzdirectory;
22
23 import java.io.IOException;
24 import java.net.URL;
25 import java.text.DateFormat;
26 import java.text.DecimalFormat;
27 import java.text.NumberFormat;
28 import java.text.ParseException;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.regex.Pattern;
40 import java.util.regex.PatternSyntaxException;
41 import org.jdtaus.banking.Bankleitzahl;
42 import org.jdtaus.banking.BankleitzahlExpirationException;
43 import org.jdtaus.banking.BankleitzahlInfo;
44 import org.jdtaus.banking.BankleitzahlenVerzeichnis;
45 import org.jdtaus.banking.messages.OutdatedBankleitzahlenVerzeichnisMessage;
46 import org.jdtaus.banking.messages.ReadsBankleitzahlenDateiMessage;
47 import org.jdtaus.banking.messages.SearchesBankleitzahlInfosMessage;
48 import org.jdtaus.banking.util.BankleitzahlenDatei;
49 import org.jdtaus.core.container.ContainerFactory;
50 import org.jdtaus.core.container.PropertyException;
51 import org.jdtaus.core.logging.spi.Logger;
52 import org.jdtaus.core.monitor.spi.Task;
53 import org.jdtaus.core.monitor.spi.TaskMonitor;
54 import org.jdtaus.core.text.Message;
55 import org.jdtaus.core.text.MessageEvent;
56 import org.jdtaus.core.text.spi.ApplicationLogger;
57
58
59
60
61
62
63
64
65
66 public class BankfileBankleitzahlenVerzeichnis implements BankleitzahlenVerzeichnis
67 {
68
69
70 private boolean initialized;
71
72
73 private BankleitzahlenDatei bankFile;
74
75
76 private final Map outdated = new HashMap( 5000 );
77
78
79 private Date dateOfExpiration;
80
81
82 private long lastModificationCheck = System.currentTimeMillis();
83
84
85 private Long reloadIntervalMillis;
86
87
88 private BankfileProvider provider;
89
90
91 private long lastModifiedMillis;
92
93
94 private Long monitoringThreshold;
95
96
97
98
99
100
101
102
103 public BankfileBankleitzahlenVerzeichnis( final long reloadIntervalMillis, final long monitoringThreshold )
104 {
105 this();
106 if ( reloadIntervalMillis > 0 )
107 {
108 this.reloadIntervalMillis = new Long( reloadIntervalMillis );
109 }
110 if ( monitoringThreshold > 0 )
111 {
112 this.monitoringThreshold = new Long( monitoringThreshold );
113 }
114 }
115
116
117
118
119
120
121 public long getReloadIntervalMillis()
122 {
123 if ( this.reloadIntervalMillis == null )
124 {
125 this.reloadIntervalMillis = this.getDefaultReloadIntervalMillis();
126 }
127
128 return this.reloadIntervalMillis.longValue();
129 }
130
131
132
133
134
135
136 public long getMonitoringThreshold()
137 {
138 if ( this.monitoringThreshold == null )
139 {
140 this.monitoringThreshold = this.getDefaultMonitoringThreshold();
141 }
142
143 return this.monitoringThreshold.longValue();
144 }
145
146 public Date getDateOfExpiration()
147 {
148 this.assertValidProperties();
149 this.assertInitialized();
150 return (Date) this.dateOfExpiration.clone();
151 }
152
153 public BankleitzahlInfo getHeadOffice( final Bankleitzahl bankCode ) throws BankleitzahlExpirationException
154 {
155 if ( bankCode == null )
156 {
157 throw new NullPointerException( "bankCode" );
158 }
159
160 this.assertValidProperties();
161 this.assertInitialized();
162
163 BankleitzahlInfo ret = null;
164 final BankleitzahlInfo[] matches = this.findByBankCode( bankCode, false );
165
166 if ( matches.length == 1 )
167 {
168 ret = matches[0];
169 }
170 else
171 {
172 this.checkReplacement( bankCode );
173 }
174
175 return ret;
176 }
177
178 public BankleitzahlInfo[] getBranchOffices( final Bankleitzahl bankCode ) throws BankleitzahlExpirationException
179 {
180 if ( bankCode == null )
181 {
182 throw new NullPointerException( "bankCode" );
183 }
184
185 this.assertValidProperties();
186 this.assertInitialized();
187
188 final BankleitzahlInfo[] matches = this.findByBankCode( bankCode, true );
189
190 if ( matches.length == 0 )
191 {
192 this.checkReplacement( bankCode );
193 }
194
195 return matches;
196 }
197
198 public final BankleitzahlInfo[] search( final String name, final String postalCode, final String city,
199 final boolean branchOffices )
200 {
201 return this.searchBankleitzahlInfos( name, postalCode, city, Boolean.valueOf( !branchOffices ),
202 Boolean.valueOf( branchOffices ) );
203
204 }
205
206 public BankleitzahlInfo[] searchBankleitzahlInfos( final String name, final String postalCode, final String city,
207 final Boolean headOffices, final Boolean branchOffices )
208 {
209 this.assertValidProperties();
210 this.assertInitialized();
211
212 final BankleitzahlInfo[] records =
213 this.bankFile == null ? new BankleitzahlInfo[ 0 ] : this.bankFile.getRecords();
214
215 final Collection col = new ArrayList( records.length );
216
217 if ( records.length > 0 )
218 {
219 final Task task = new Task();
220 task.setCancelable( true );
221 task.setDescription( new SearchesBankleitzahlInfosMessage() );
222 task.setIndeterminate( false );
223 task.setMinimum( 0 );
224 task.setMaximum( records.length - 1 );
225 task.setProgress( 0 );
226
227 try
228 {
229 if ( task.getMaximum() > this.getMonitoringThreshold() )
230 {
231 this.getTaskMonitor().monitor( task );
232 }
233
234 final NumberFormat plzFmt = new DecimalFormat( "00000" );
235 final Pattern namePattern =
236 name != null ? Pattern.compile( ".*" + name.toUpperCase() + ".*" ) : null;
237
238 final Pattern postalPattern =
239 postalCode != null ? Pattern.compile( ".*" + postalCode.toUpperCase() + ".*" ) : null;
240
241 final Pattern cityPattern =
242 city != null ? Pattern.compile( ".*" + city.toUpperCase() + ".*" ) : null;
243
244 for ( int i = records.length - 1; i >= 0 && !task.isCancelled(); i-- )
245 {
246 final String plz = plzFmt.format( records[i].getPostalCode() );
247 task.setProgress( task.getMaximum() - i );
248
249 if ( ( namePattern == null
250 ? true : namePattern.matcher( records[i].getName().toUpperCase() ).matches() )
251 && ( postalPattern == null
252 ? true : postalPattern.matcher( plz ).matches() )
253 && ( cityPattern == null
254 ? true : cityPattern.matcher( records[i].getCity().toUpperCase() ).matches() )
255 && ( headOffices == null
256 ? true : records[i].isHeadOffice() == headOffices.booleanValue() )
257 && ( branchOffices == null
258 ? true : records[i].isHeadOffice() != branchOffices.booleanValue() ) )
259 {
260 col.add( records[i].clone() );
261 }
262 }
263
264 if ( task.isCancelled() )
265 {
266 col.clear();
267 }
268 }
269 catch ( final PatternSyntaxException e )
270 {
271 throw (IllegalArgumentException) new IllegalArgumentException( e.getMessage() ).initCause( e );
272 }
273 finally
274 {
275 if ( task.getMaximum() > this.getMonitoringThreshold() )
276 {
277 this.getTaskMonitor().finish( task );
278 }
279 }
280 }
281
282 return (BankleitzahlInfo[]) col.toArray( new BankleitzahlInfo[ col.size() ] );
283 }
284
285
286
287
288
289
290
291
292 private synchronized void assertInitialized()
293 {
294 Task task = null;
295 boolean logExpirationMessage = false;
296
297 try
298 {
299 if ( this.provider == null
300 || System.currentTimeMillis() - this.lastModificationCheck > this.getReloadIntervalMillis() )
301 {
302 this.lastModificationCheck = System.currentTimeMillis();
303 if ( this.provider == null || this.provider.getLastModifiedMillis() != this.lastModifiedMillis )
304 {
305 this.outdated.clear();
306 this.bankFile = null;
307 this.dateOfExpiration = null;
308 this.initialized = false;
309
310 if ( this.provider != null )
311 {
312 this.getLogger().info( this.getReloadInfoMessage( this.getLocale(), new Date(
313 this.lastModifiedMillis ), new Date( this.provider.getLastModifiedMillis() ) ) );
314
315 }
316 }
317 }
318
319 if ( !this.initialized )
320 {
321 final DateFormat dateFormat = new SimpleDateFormat( this.getDateOfExpirationPattern() );
322 this.dateOfExpiration = dateFormat.parse( this.getDateOfExpirationText() );
323 final BankfileProvider bankfileProvider = this.getLatestBankfileProvider();
324
325 if ( bankfileProvider != null && bankfileProvider.getBankfileCount() > 0 )
326 {
327 this.provider = bankfileProvider;
328 this.lastModifiedMillis = bankfileProvider.getLastModifiedMillis();
329 this.dateOfExpiration =
330 bankfileProvider.getDateOfExpiration( bankfileProvider.getBankfileCount() - 1 );
331
332 final URL[] rsrc = new URL[ bankfileProvider.getBankfileCount() ];
333 for ( int i = 0; i < rsrc.length; i++ )
334 {
335 rsrc[i] = bankfileProvider.getBankfile( i );
336 }
337
338 task = new Task();
339 task.setIndeterminate( false );
340 task.setCancelable( false );
341 task.setDescription( new ReadsBankleitzahlenDateiMessage() );
342 task.setMinimum( 0 );
343 task.setProgress( 0 );
344 task.setMaximum( rsrc.length );
345 this.getTaskMonitor().monitor( task );
346
347 int progress = 0;
348 long processedRecords = 0L;
349 task.setProgress( progress++ );
350 this.bankFile = new BankleitzahlenDatei( rsrc[0] );
351 processedRecords += this.bankFile.getRecords().length;
352 for ( int i = 1; i < rsrc.length; i++ )
353 {
354 task.setProgress( progress++ );
355 final BankleitzahlenDatei update = new BankleitzahlenDatei( rsrc[i] );
356
357
358 final BankleitzahlInfo[] records = this.bankFile.getRecords();
359
360 for ( int j = records.length - 1; j >= 0; j-- )
361 {
362 if ( records[j].getChangeLabel() == 'D'
363 && update.getRecord( records[j].getSerialNumber() ) == null )
364 {
365 List l = (List) this.outdated.get( records[j].getBankCode() );
366
367 if ( l == null )
368 {
369 l = new LinkedList();
370 this.outdated.put( records[j].getBankCode(), l );
371 }
372
373 l.add( records[j] );
374 }
375 }
376
377 this.bankFile.update( update );
378 processedRecords += update.getRecords().length;
379 }
380
381
382 for ( final Iterator it = this.outdated.keySet().iterator(); it.hasNext(); )
383 {
384 final Bankleitzahl key = (Bankleitzahl) it.next();
385 if ( this.findByBankCode( key, false ).length > 0 )
386 {
387 it.remove();
388 }
389 }
390
391
392 if ( this.getLogger().isDebugEnabled() )
393 {
394 for ( final Iterator it = this.outdated.keySet().iterator(); it.hasNext(); )
395 {
396 final Bankleitzahl blz = (Bankleitzahl) it.next();
397 this.getLogger().debug( this.getOutdatedInfoMessage(
398 this.getLocale(), blz.format( Bankleitzahl.LETTER_FORMAT ) ) );
399
400 }
401 }
402
403 logExpirationMessage = true;
404 this.initialized = true;
405
406 this.getLogger().info( this.getBankfileInfoMessage(
407 this.getLocale(), new Long( processedRecords ), new Integer( rsrc.length ) ) );
408
409 }
410 else
411 {
412 this.getLogger().warn( this.getNoBankfilesFoundMessage( this.getLocale() ) );
413 }
414 }
415 }
416 catch ( final ParseException e )
417 {
418 throw new RuntimeException( e );
419 }
420 catch ( final IOException e )
421 {
422 throw new RuntimeException( e );
423 }
424 finally
425 {
426 if ( task != null )
427 {
428 this.getTaskMonitor().finish( task );
429 }
430 }
431
432
433 if ( logExpirationMessage )
434 {
435 if ( new Date().after( this.getDateOfExpiration() ) )
436 {
437 this.getApplicationLogger().log( new MessageEvent( this, new Message[]
438 {
439 new OutdatedBankleitzahlenVerzeichnisMessage( this.getDateOfExpiration() )
440 }, MessageEvent.WARNING ) );
441
442 }
443 }
444 }
445
446
447
448
449
450
451 private void assertValidProperties()
452 {
453 if ( this.getReloadIntervalMillis() < 0L )
454 {
455 throw new PropertyException( "reloadIntervalMillis", Long.toString( this.getReloadIntervalMillis() ) );
456 }
457 if ( this.getDateOfExpirationText() == null || this.getDateOfExpirationText().length() == 0 )
458 {
459 throw new PropertyException( "dateOfExpirationText", this.getDateOfExpirationText() );
460 }
461 if ( this.getDateOfExpirationPattern() == null || this.getDateOfExpirationPattern().length() == 0 )
462 {
463 throw new PropertyException( "dateOfExpirationPattern", this.getDateOfExpirationPattern() );
464 }
465
466 try
467 {
468 final DateFormat dateFormat = new SimpleDateFormat( this.getDateOfExpirationPattern() );
469 dateFormat.parse( this.getDateOfExpirationText() );
470 }
471 catch ( final ParseException e )
472 {
473 throw new PropertyException( "dateOfExpirationText", this.getDateOfExpirationText(), e );
474 }
475 }
476
477
478
479
480
481
482
483
484
485 private void checkReplacement( final Bankleitzahl bankCode ) throws BankleitzahlExpirationException
486 {
487 if ( bankCode == null )
488 {
489 throw new NullPointerException( "bankCode" );
490 }
491
492 final List l = (List) this.outdated.get( bankCode );
493
494 if ( l != null )
495 {
496
497 BankleitzahlInfo current = null;
498 BankleitzahlInfo record = null;
499
500 for ( final Iterator it = l.iterator(); it.hasNext(); )
501 {
502 current = (BankleitzahlInfo) it.next();
503 if ( current.getReplacingBankCode() != null )
504 {
505 record = current;
506 }
507 }
508
509
510 if ( record != null )
511 {
512 final BankleitzahlInfo[] replacement = this.findByBankCode( record.getReplacingBankCode(), false );
513 assert replacement.length == 0 || replacement.length == 1 :
514 "Multiple head offices for '" + record.getReplacingBankCode() + "'.";
515
516 if ( replacement.length == 1 )
517 {
518 throw new BankleitzahlExpirationException( record, replacement[0] );
519 }
520 }
521 }
522 }
523
524
525
526
527
528
529
530
531
532
533
534
535 private BankfileProvider getLatestBankfileProvider() throws IOException
536 {
537 final BankfileProvider[] providers = this.getBankfileProvider();
538 BankfileProvider latest = null;
539
540 for ( int i = providers.length - 1; i >= 0; i-- )
541 {
542 if ( providers[i].getBankfileCount() > 0
543 && ( latest == null || latest.getDateOfExpiration( latest.getBankfileCount() - 1 ).
544 before( providers[i].getDateOfExpiration( providers[i].getBankfileCount() - 1 ) ) ) )
545 {
546 latest = providers[i];
547 }
548 }
549
550 return latest;
551 }
552
553
554
555
556
557
558
559
560 private BankleitzahlInfo[] findByBankCode( final Bankleitzahl bankCode, final boolean branchOffices )
561 {
562 final BankleitzahlInfo[] records =
563 this.bankFile == null ? new BankleitzahlInfo[ 0 ] : this.bankFile.getRecords();
564
565 final Collection col = new ArrayList( records.length );
566
567 for ( int i = records.length - 1; i >= 0; i-- )
568 {
569 if ( records[i].getBankCode().equals( bankCode ) && records[i].isHeadOffice() != branchOffices
570 && !col.add( records[i] ) )
571 {
572 throw new IllegalStateException( this.getDuplicateRecordMessage(
573 this.getLocale(), records[i].getSerialNumber(), bankCode.format( Bankleitzahl.LETTER_FORMAT ) ) );
574
575 }
576 }
577
578 return (BankleitzahlInfo[]) col.toArray( new BankleitzahlInfo[ col.size() ] );
579 }
580
581
582
583
584
585
586
587 public BankfileBankleitzahlenVerzeichnis()
588 {
589 super();
590 }
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605 private Logger getLogger()
606 {
607 return (Logger) ContainerFactory.getContainer().
608 getDependency( this, "Logger" );
609
610 }
611
612
613
614
615
616
617 private ApplicationLogger getApplicationLogger()
618 {
619 return (ApplicationLogger) ContainerFactory.getContainer().
620 getDependency( this, "ApplicationLogger" );
621
622 }
623
624
625
626
627
628
629 private TaskMonitor getTaskMonitor()
630 {
631 return (TaskMonitor) ContainerFactory.getContainer().
632 getDependency( this, "TaskMonitor" );
633
634 }
635
636
637
638
639
640
641 private BankfileProvider[] getBankfileProvider()
642 {
643 return (BankfileProvider[]) ContainerFactory.getContainer().
644 getDependency( this, "BankfileProvider" );
645
646 }
647
648
649
650
651
652
653 private Locale getLocale()
654 {
655 return (Locale) ContainerFactory.getContainer().
656 getDependency( this, "Locale" );
657
658 }
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673 private java.lang.Long getDefaultReloadIntervalMillis()
674 {
675 return (java.lang.Long) ContainerFactory.getContainer().
676 getProperty( this, "defaultReloadIntervalMillis" );
677
678 }
679
680
681
682
683
684
685 private java.lang.Long getDefaultMonitoringThreshold()
686 {
687 return (java.lang.Long) ContainerFactory.getContainer().
688 getProperty( this, "defaultMonitoringThreshold" );
689
690 }
691
692
693
694
695
696
697 private java.lang.String getDateOfExpirationText()
698 {
699 return (java.lang.String) ContainerFactory.getContainer().
700 getProperty( this, "dateOfExpirationText" );
701
702 }
703
704
705
706
707
708
709 private java.lang.String getDateOfExpirationPattern()
710 {
711 return (java.lang.String) ContainerFactory.getContainer().
712 getProperty( this, "dateOfExpirationPattern" );
713
714 }
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734 private String getOutdatedInfoMessage( final Locale locale,
735 final java.lang.String bankleitzahl )
736 {
737 return ContainerFactory.getContainer().
738 getMessage( this, "outdatedInfo", locale,
739 new Object[]
740 {
741 bankleitzahl
742 });
743
744 }
745
746
747
748
749
750
751
752
753
754
755
756
757 private String getDuplicateRecordMessage( final Locale locale,
758 final java.lang.Number serialNumber,
759 final java.lang.String bankleitzahl )
760 {
761 return ContainerFactory.getContainer().
762 getMessage( this, "duplicateRecord", locale,
763 new Object[]
764 {
765 serialNumber,
766 bankleitzahl
767 });
768
769 }
770
771
772
773
774
775
776
777
778
779
780
781
782 private String getBankfileInfoMessage( final Locale locale,
783 final java.lang.Number entityCount,
784 final java.lang.Number bankfileCount )
785 {
786 return ContainerFactory.getContainer().
787 getMessage( this, "bankfileInfo", locale,
788 new Object[]
789 {
790 entityCount,
791 bankfileCount
792 });
793
794 }
795
796
797
798
799
800
801
802
803
804
805
806
807 private String getReloadInfoMessage( final Locale locale,
808 final java.util.Date lastModification,
809 final java.util.Date lastProviderModification )
810 {
811 return ContainerFactory.getContainer().
812 getMessage( this, "reloadInfo", locale,
813 new Object[]
814 {
815 lastModification,
816 lastProviderModification
817 });
818
819 }
820
821
822
823
824
825
826
827
828
829
830 private String getNoBankfilesFoundMessage( final Locale locale )
831 {
832 return ContainerFactory.getContainer().
833 getMessage( this, "noBankfilesFound", locale, null );
834
835 }
836
837
838
839
840 }