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.currencydir;
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.Currency;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.HashSet;
40 import java.util.Iterator;
41 import java.util.LinkedList;
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.messages.ReadsCurrenciesMessage;
48 import org.jdtaus.banking.messages.SearchesCurrenciesMessage;
49 import org.jdtaus.banking.spi.CurrencyMapper;
50 import org.jdtaus.banking.spi.UnsupportedCurrencyException;
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
66
67
68
69
70
71
72
73 public class JaxpCurrencyDirectory implements CurrencyMapper
74 {
75
76
77 private static final String SCHEMA_LANGUAGE_KEY = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
78
79
80 private static final String SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
81
82
83 private static final String CURRENCIES_NS = "http://jdtaus.org/banking/xml/currencies";
84
85
86 private static final String BANKING_NS = "http://jdtaus.org/banking/model";
87
88
89
90
91
92 private static final String[] SUPPORTED_VERSIONS =
93 {
94 "1.0"
95 };
96
97
98 private boolean initialized;
99
100
101 private final Map isoMap = new HashMap();
102
103
104 private final Map dtausMap = new HashMap();
105
106
107 private final Map monitorMap = new HashMap();
108
109
110 private long lastCheck = System.currentTimeMillis();
111
112
113 private Long reloadIntervalMillis;
114
115
116 private Long monitoringThreshold;
117
118
119
120
121
122
123
124
125 public JaxpCurrencyDirectory( final long reloadIntervalMillis, final long monitoringThreshold )
126 {
127 this();
128 if ( reloadIntervalMillis > 0 )
129 {
130 this.reloadIntervalMillis = new Long( reloadIntervalMillis );
131 }
132 if ( monitoringThreshold > 0 )
133 {
134 this.monitoringThreshold = new Long( monitoringThreshold );
135 }
136 }
137
138
139
140
141
142
143 public long getReloadIntervalMillis()
144 {
145 if ( this.reloadIntervalMillis == null )
146 {
147 this.reloadIntervalMillis = this.getDefaultReloadIntervalMillis();
148 }
149
150 return this.reloadIntervalMillis.longValue();
151 }
152
153
154
155
156
157
158 public long getMonitoringThreshold()
159 {
160 if ( this.monitoringThreshold == null )
161 {
162 this.monitoringThreshold = this.getDefaultMonitoringThreshold();
163 }
164
165 return this.monitoringThreshold.longValue();
166 }
167
168 public Currency[] getDtausCurrencies( final Date date )
169 {
170 if ( date == null )
171 {
172 throw new NullPointerException( "date" );
173 }
174
175 this.assertValidProperties();
176 this.assertInitialized();
177
178 final Collection col = new LinkedList();
179
180 if ( !this.isoMap.isEmpty() )
181 {
182 final Task task = new Task();
183 task.setCancelable( true );
184 task.setDescription( new SearchesCurrenciesMessage() );
185 task.setIndeterminate( false );
186 task.setMaximum( this.isoMap.size() );
187 task.setMinimum( 0 );
188
189 int progress = 0;
190 task.setProgress( progress );
191
192 try
193 {
194 if ( task.getMaximum() > this.getMonitoringThreshold() )
195 {
196 this.getTaskMonitor().monitor( task );
197 }
198
199 for ( final Iterator it = this.isoMap.keySet().iterator(); it.hasNext() && !task.isCancelled(); )
200 {
201 task.setProgress( progress++ );
202 final String isoCode = (String) it.next();
203 final JaxpCurrency currency = (JaxpCurrency) this.isoMap.get( isoCode );
204
205 if ( currency.isValidAt( date ) )
206 {
207 col.add( Currency.getInstance( isoCode ) );
208 }
209 }
210
211 if ( task.isCancelled() )
212 {
213 col.clear();
214 }
215 }
216 finally
217 {
218 if ( task.getMaximum() > this.getMonitoringThreshold() )
219 {
220 this.getTaskMonitor().finish( task );
221 }
222 }
223 }
224
225 return (Currency[]) col.toArray( new Currency[ col.size() ] );
226 }
227
228 public Currency getDtausCurrency( final char code, final Date date )
229 {
230 if ( date == null )
231 {
232 throw new NullPointerException( "date" );
233 }
234
235 this.assertValidProperties();
236 this.assertInitialized();
237
238 final JaxpCurrency currency = (JaxpCurrency) this.dtausMap.get( new Character( code ) );
239 Currency ret = null;
240
241 if ( currency != null && currency.isValidAt( date ) )
242 {
243 ret = Currency.getInstance( currency.getIsoCode() );
244 }
245
246 return ret;
247 }
248
249 public char getDtausCode( final Currency currency, final Date date )
250 {
251 if ( currency == null )
252 {
253 throw new NullPointerException( "currency" );
254 }
255 if ( date == null )
256 {
257 throw new NullPointerException( "date" );
258 }
259
260 this.assertValidProperties();
261 this.assertInitialized();
262
263 final JaxpCurrency xml = (JaxpCurrency) this.isoMap.get( currency.getCurrencyCode() );
264
265 if ( xml != null && xml.getDtausCode() != null && xml.isValidAt( date ) )
266 {
267 return xml.getDtausCode().charValue();
268 }
269
270 throw new UnsupportedCurrencyException( currency.getCurrencyCode(), date );
271 }
272
273
274
275
276
277
278
279
280 private synchronized void assertInitialized()
281 {
282 try
283 {
284 if ( System.currentTimeMillis() - this.lastCheck > this.getReloadIntervalMillis()
285 && !this.monitorMap.isEmpty() )
286 {
287 this.lastCheck = System.currentTimeMillis();
288 for ( final Iterator it = this.monitorMap.entrySet().iterator(); it.hasNext(); )
289 {
290 final Map.Entry entry = (Map.Entry) it.next();
291 final File file = (File) entry.getKey();
292 final Long lastModified = (Long) entry.getValue();
293 assert lastModified != null : "Expected modification time.";
294
295 if ( file.lastModified() != lastModified.longValue() )
296 {
297 this.getLogger().info( this.getChangeInfoMessage( this.getLocale(), file.getAbsolutePath() ) );
298 this.initialized = false;
299 break;
300 }
301 }
302 }
303
304 if ( !this.initialized )
305 {
306 this.monitorMap.clear();
307 this.isoMap.clear();
308 this.dtausMap.clear();
309
310 final Document[] docs = this.parseResources();
311 final Collection col = new LinkedList();
312
313 for ( int i = docs.length - 1; i >= 0; i-- )
314 {
315 col.addAll( Arrays.asList( this.transformDocument( docs[i] ) ) );
316 }
317
318 for ( final Iterator it = col.iterator(); it.hasNext(); )
319 {
320 final JaxpCurrency currency = (JaxpCurrency) it.next();
321 if ( this.isoMap.put( currency.getIsoCode(), currency ) != null
322 || ( currency.getDtausCode() != null
323 && this.dtausMap.put( currency.getDtausCode(), currency ) != null ) )
324 {
325 throw new IllegalStateException( this.getDuplicateCurrencyMessage(
326 this.getLocale(), currency.getIsoCode(),
327 Currency.getInstance( currency.getIsoCode() ).getSymbol( this.getLocale() ) ) );
328
329 }
330 }
331
332 this.getLogger().info( this.getCurrencyInfoMessage(
333 this.getLocale(), new Integer( this.isoMap.size() ), new Integer( docs.length ) ) );
334
335 this.initialized = true;
336 }
337 }
338 catch ( final IOException e )
339 {
340 this.initialized = false;
341 throw new RuntimeException( e );
342 }
343 catch ( final SAXException e )
344 {
345 this.initialized = false;
346 throw new RuntimeException( e );
347 }
348 catch ( final ParserConfigurationException e )
349 {
350 this.initialized = false;
351 throw new RuntimeException( e );
352 }
353 catch ( final ParseException e )
354 {
355 this.initialized = false;
356 throw new RuntimeException( e );
357 }
358 }
359
360
361
362
363
364
365 private void assertValidProperties()
366 {
367 if ( this.getReloadIntervalMillis() < 0L )
368 {
369 throw new PropertyException( "reloadIntervalMillis", Long.toString( this.getReloadIntervalMillis() ) );
370 }
371 if ( this.getMonitoringThreshold() < 0L )
372 {
373 throw new PropertyException( "monitoringThreshold", Long.toString( this.getMonitoringThreshold() ) );
374 }
375 }
376
377
378
379
380
381
382
383
384
385
386 private URL[] getResources() throws IOException
387 {
388 final Collection resources = new HashSet();
389 final JaxpCurrenciesProvider[] provider = this.getCurrenciesProvider();
390
391 for ( int i = provider.length - 1; i >= 0; i-- )
392 {
393 resources.addAll( Arrays.asList( provider[i].getResources() ) );
394 }
395
396 return (URL[]) resources.toArray( new URL[ resources.size() ] );
397 }
398
399
400
401
402
403
404
405
406 private void monitorResource( final URL url )
407 {
408 if ( url == null )
409 {
410 throw new NullPointerException( "url" );
411 }
412
413 try
414 {
415 final File file = new File( new URI( url.toString() ) );
416 this.monitorMap.put( file, new Long( file.lastModified() ) );
417 this.getLogger().info( this.getMonitoringInfoMessage( this.getLocale(), file.getAbsolutePath() ) );
418 }
419 catch ( final IllegalArgumentException e )
420 {
421 this.getLogger().info( this.getNotMonitoringWarningMessage(
422 this.getLocale(), url.toExternalForm(), e.getMessage() ) );
423
424 }
425 catch ( final URISyntaxException e )
426 {
427 this.getLogger().info( this.getNotMonitoringWarningMessage(
428 this.getLocale(), url.toExternalForm(), e.getMessage() ) );
429
430 }
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444
445 private Document[] parseResources() throws IOException, ParserConfigurationException, SAXException
446 {
447 InputStream stream = null;
448
449 final URL[] resources = this.getResources();
450 final Document[] ret = new Document[ resources.length ];
451
452 if ( resources.length > 0 )
453 {
454 final DocumentBuilder validatingParser = this.getDocumentBuilder();
455 final DocumentBuilderFactory namespaceAwareFactory = DocumentBuilderFactory.newInstance();
456 namespaceAwareFactory.setNamespaceAware( true );
457 final DocumentBuilder nonValidatingParser = namespaceAwareFactory.newDocumentBuilder();
458
459 final Task task = new Task();
460 task.setCancelable( false );
461 task.setDescription( new ReadsCurrenciesMessage() );
462 task.setIndeterminate( false );
463 task.setMaximum( resources.length - 1 );
464 task.setMinimum( 0 );
465 task.setProgress( 0 );
466
467 try
468 {
469 this.getTaskMonitor().monitor( task );
470
471 for ( int i = resources.length - 1; i >= 0; i-- )
472 {
473 task.setProgress( task.getMaximum() - i );
474
475 final URL resource = resources[i];
476 final ErrorHandler errorHandler = new ErrorHandler()
477 {
478
479 public void warning( final SAXParseException e ) throws SAXException
480 {
481 getLogger().warn( getParseExceptionMessage(
482 getLocale(), resource.toExternalForm(),
483 e.getMessage(), new Integer( e.getLineNumber() ),
484 new Integer( e.getColumnNumber() ) ) );
485
486 }
487
488 public void error( final SAXParseException e ) throws SAXException
489 {
490 throw new SAXException( getParseExceptionMessage(
491 getLocale(), resource.toExternalForm(),
492 e.getMessage(), new Integer( e.getLineNumber() ),
493 new Integer( e.getColumnNumber() ) ), e );
494
495 }
496
497 public void fatalError( final SAXParseException e ) throws SAXException
498 {
499 throw new SAXException( getParseExceptionMessage(
500 getLocale(), resource.toExternalForm(),
501 e.getMessage(), new Integer( e.getLineNumber() ),
502 new Integer( e.getColumnNumber() ) ), e );
503
504 }
505
506 };
507
508 nonValidatingParser.setErrorHandler( errorHandler );
509 validatingParser.setErrorHandler( errorHandler );
510
511 try
512 {
513 this.monitorResource( resources[i] );
514 stream = resources[i].openStream();
515 ret[i] = nonValidatingParser.parse( stream );
516
517 if ( ret[i].getDocumentElement().hasAttributeNS( XSI_NS, "schemaLocation" ) )
518 {
519 stream.close();
520 stream = resources[i].openStream();
521 ret[i] = validatingParser.parse( stream );
522 }
523 else if ( this.getLogger().isInfoEnabled() )
524 {
525 this.getLogger().info( this.getNoSchemaLocationMessage(
526 this.getLocale(), resources[i].toExternalForm() ) );
527
528 }
529 }
530 finally
531 {
532 if ( stream != null )
533 {
534 stream.close();
535 stream = null;
536 }
537 }
538 }
539 }
540 finally
541 {
542 this.getTaskMonitor().finish( task );
543 }
544 }
545 else
546 {
547 this.getLogger().warn( this.getNoCurrenciesFoundMessage( this.getLocale() ) );
548 }
549
550 return ret;
551 }
552
553
554
555
556
557
558
559
560
561
562 private JaxpCurrency[] transformDocument( final Document doc ) throws ParseException
563 {
564 final Calendar cal = Calendar.getInstance();
565 final DateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" );
566 final Collection col = new ArrayList( 500 );
567
568 String modelVersion = null;
569 final String namespace = doc.getDocumentElement().getNamespaceURI();
570
571 if ( namespace == null )
572 {
573 throw new RuntimeException( this.getUnsupportedNamespaceMessage( this.getLocale(), null ) );
574 }
575 else if ( CURRENCIES_NS.equals( namespace ) )
576 {
577 modelVersion = doc.getDocumentElement().getAttributeNS( namespace, "version" );
578 }
579 else if ( BANKING_NS.equals( namespace ) )
580 {
581 modelVersion = doc.getDocumentElement().getAttributeNS( namespace, "modelVersion" );
582 }
583 else
584 {
585 throw new RuntimeException( this.getUnsupportedNamespaceMessage( this.getLocale(), namespace ) );
586 }
587
588 boolean supportedModelVersion = false;
589 for ( int i = SUPPORTED_VERSIONS.length - 1; i >= 0; i-- )
590 {
591 if ( SUPPORTED_VERSIONS[i].equals( modelVersion ) )
592 {
593 supportedModelVersion = true;
594 break;
595 }
596 }
597
598 if ( !supportedModelVersion )
599 {
600 throw new RuntimeException( this.getUnsupportedModelVersionMessage( this.getLocale(), modelVersion ) );
601 }
602
603 final NodeList l = doc.getDocumentElement().getElementsByTagNameNS( namespace, "currency" );
604
605 for ( int i = l.getLength() - 1; i >= 0; i-- )
606 {
607 final Element e = (Element) l.item( i );
608
609 if ( e.getParentNode().equals( doc.getDocumentElement() ) )
610 {
611 final JaxpCurrency cur = new JaxpCurrency();
612 cur.setIsoCode( e.getAttributeNS( namespace, "isoCode" ) );
613
614 if ( e.hasAttributeNS( namespace, "dtausCode" ) )
615 {
616 cur.setDtausCode( new Character( e.getAttributeNS( namespace, "dtausCode" ).charAt( 0 ) ) );
617 }
618
619 cal.setTime( dateFormat.parse( e.getAttributeNS( namespace, "startDate" ) ) );
620 cal.set( Calendar.HOUR_OF_DAY, 0 );
621 cal.set( Calendar.MINUTE, 0 );
622 cal.set( Calendar.SECOND, 0 );
623 cal.set( Calendar.MILLISECOND, 0 );
624 cur.setStartDate( cal.getTime() );
625
626 if ( e.hasAttributeNS( namespace, "endDate" ) )
627 {
628 cal.setTime( dateFormat.parse( e.getAttributeNS( namespace, "endDate" ) ) );
629 cal.set( Calendar.HOUR_OF_DAY, 23 );
630 cal.set( Calendar.MINUTE, 59 );
631 cal.set( Calendar.SECOND, 59 );
632 cal.set( Calendar.MILLISECOND, 999 );
633 cur.setEndDate( cal.getTime() );
634 }
635
636 col.add( cur );
637 }
638 }
639
640 return (JaxpCurrency[]) col.toArray( new JaxpCurrency[ col.size() ] );
641 }
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656 private DocumentBuilder getDocumentBuilder() throws ParserConfigurationException
657 {
658 final DocumentBuilder xmlBuilder;
659 final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
660 xmlFactory.setNamespaceAware( true );
661
662 try
663 {
664 xmlFactory.setValidating( true );
665 xmlFactory.setAttribute( SCHEMA_LANGUAGE_KEY, SCHEMA_LANGUAGE );
666 }
667 catch ( final IllegalArgumentException e )
668 {
669 this.getLogger().info( this.getNoJAXPValidationWarningMessage( this.getLocale(), e.getMessage() ) );
670 xmlFactory.setValidating( false );
671 }
672
673 xmlBuilder = xmlFactory.newDocumentBuilder();
674 xmlBuilder.setEntityResolver( new EntityResolverChain() );
675 return xmlBuilder;
676 }
677
678
679
680
681
682
683
684 public JaxpCurrencyDirectory()
685 {
686 super();
687 }
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702 private Logger getLogger()
703 {
704 return (Logger) ContainerFactory.getContainer().
705 getDependency( this, "Logger" );
706
707 }
708
709
710
711
712
713
714 private JaxpCurrenciesProvider[] getCurrenciesProvider()
715 {
716 return (JaxpCurrenciesProvider[]) ContainerFactory.getContainer().
717 getDependency( this, "CurrenciesProvider" );
718
719 }
720
721
722
723
724
725
726 private TaskMonitor getTaskMonitor()
727 {
728 return (TaskMonitor) ContainerFactory.getContainer().
729 getDependency( this, "TaskMonitor" );
730
731 }
732
733
734
735
736
737
738 private Locale getLocale()
739 {
740 return (Locale) ContainerFactory.getContainer().
741 getDependency( this, "Locale" );
742
743 }
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758 private java.lang.Long getDefaultReloadIntervalMillis()
759 {
760 return (java.lang.Long) ContainerFactory.getContainer().
761 getProperty( this, "defaultReloadIntervalMillis" );
762
763 }
764
765
766
767
768
769
770 private java.lang.Long getDefaultMonitoringThreshold()
771 {
772 return (java.lang.Long) ContainerFactory.getContainer().
773 getProperty( this, "defaultMonitoringThreshold" );
774
775 }
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795 private String getNoJAXPValidationWarningMessage( final Locale locale,
796 final java.lang.String detailMessage )
797 {
798 return ContainerFactory.getContainer().
799 getMessage( this, "noJAXPValidationWarning", locale,
800 new Object[]
801 {
802 detailMessage
803 });
804
805 }
806
807
808
809
810
811
812
813
814
815
816
817
818 private String getNotMonitoringWarningMessage( final Locale locale,
819 final java.lang.String resourceName,
820 final java.lang.String detailMessage )
821 {
822 return ContainerFactory.getContainer().
823 getMessage( this, "notMonitoringWarning", locale,
824 new Object[]
825 {
826 resourceName,
827 detailMessage
828 });
829
830 }
831
832
833
834
835
836
837
838
839
840
841
842 private String getChangeInfoMessage( final Locale locale,
843 final java.lang.String resourceName )
844 {
845 return ContainerFactory.getContainer().
846 getMessage( this, "changeInfo", locale,
847 new Object[]
848 {
849 resourceName
850 });
851
852 }
853
854
855
856
857
858
859
860
861
862
863
864 private String getMonitoringInfoMessage( final Locale locale,
865 final java.lang.String resourceName )
866 {
867 return ContainerFactory.getContainer().
868 getMessage( this, "monitoringInfo", locale,
869 new Object[]
870 {
871 resourceName
872 });
873
874 }
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889 private String getParseExceptionMessage( final Locale locale,
890 final java.lang.String resourceName,
891 final java.lang.String cause,
892 final java.lang.Number line,
893 final java.lang.Number column )
894 {
895 return ContainerFactory.getContainer().
896 getMessage( this, "parseException", locale,
897 new Object[]
898 {
899 resourceName,
900 cause,
901 line,
902 column
903 });
904
905 }
906
907
908
909
910
911
912
913
914
915
916
917 private String getUnsupportedNamespaceMessage( final Locale locale,
918 final java.lang.String namespace )
919 {
920 return ContainerFactory.getContainer().
921 getMessage( this, "unsupportedNamespace", locale,
922 new Object[]
923 {
924 namespace
925 });
926
927 }
928
929
930
931
932
933
934
935
936
937
938
939 private String getUnsupportedModelVersionMessage( final Locale locale,
940 final java.lang.String modelVersion )
941 {
942 return ContainerFactory.getContainer().
943 getMessage( this, "unsupportedModelVersion", locale,
944 new Object[]
945 {
946 modelVersion
947 });
948
949 }
950
951
952
953
954
955
956
957
958
959
960
961
962 private String getCurrencyInfoMessage( final Locale locale,
963 final java.lang.Number entityCount,
964 final java.lang.Number documentCount )
965 {
966 return ContainerFactory.getContainer().
967 getMessage( this, "currencyInfo", locale,
968 new Object[]
969 {
970 entityCount,
971 documentCount
972 });
973
974 }
975
976
977
978
979
980
981
982
983
984
985
986 private String getNoSchemaLocationMessage( final Locale locale,
987 final java.lang.String resource )
988 {
989 return ContainerFactory.getContainer().
990 getMessage( this, "noSchemaLocation", locale,
991 new Object[]
992 {
993 resource
994 });
995
996 }
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009 private String getDuplicateCurrencyMessage( final Locale locale,
1010 final java.lang.String currencyCode,
1011 final java.lang.String currencySymbol )
1012 {
1013 return ContainerFactory.getContainer().
1014 getMessage( this, "duplicateCurrency", locale,
1015 new Object[]
1016 {
1017 currencyCode,
1018 currencySymbol
1019 });
1020
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032 private String getNoCurrenciesFoundMessage( final Locale locale )
1033 {
1034 return ContainerFactory.getContainer().
1035 getMessage( this, "noCurrenciesFound", locale, null );
1036
1037 }
1038
1039
1040
1041
1042 }