001/* 002 * jDTAUS Banking RI DTAUS 003 * Copyright (C) 2005 Christian Schulte 004 * <cs@schulte.it> 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License, or any later version. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 019 * 020 */ 021package org.jdtaus.banking.dtaus.ri.zka; 022 023import java.io.EOFException; 024import java.io.File; 025import java.io.IOException; 026import java.io.RandomAccessFile; 027import java.util.Iterator; 028import java.util.Locale; 029import java.util.Map; 030import org.jdtaus.banking.dtaus.CorruptedException; 031import org.jdtaus.banking.dtaus.PhysicalFile; 032import org.jdtaus.banking.dtaus.PhysicalFileException; 033import org.jdtaus.banking.dtaus.PhysicalFileFactory; 034import org.jdtaus.banking.dtaus.spi.Fields; 035import org.jdtaus.banking.messages.IllegalDataMessage; 036import org.jdtaus.banking.messages.IllegalFileLengthMessage; 037import org.jdtaus.core.container.ContainerFactory; 038import org.jdtaus.core.container.Implementation; 039import org.jdtaus.core.container.ModelFactory; 040import org.jdtaus.core.container.PropertyException; 041import org.jdtaus.core.io.FileOperations; 042import org.jdtaus.core.io.util.CoalescingFileOperations; 043import org.jdtaus.core.io.util.RandomAccessFileOperations; 044import org.jdtaus.core.io.util.ReadAheadFileOperations; 045import org.jdtaus.core.nio.util.Charsets; 046import org.jdtaus.core.text.Message; 047 048/** 049 * Default {@code PhysicalFileFactory} implementation. 050 * 051 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a> 052 * @version $JDTAUS: DefaultPhysicalFileFactory.java 8865 2014-01-10 17:13:42Z schulte $ 053 */ 054public final class DefaultPhysicalFileFactory implements PhysicalFileFactory 055{ 056 057 /** 058 * Constant for the name of attribute {@code readAheadCaching}. 059 * <p> 060 * The {@code readAheadCaching} attribute is used to enabled or disable the 061 * use of a read-ahead caching algorithm. Its expected value is of type 062 * {@code Boolean}. 063 * </p> 064 */ 065 public static final String ATTRIBUTE_READAHEAD_CACHING = 066 DefaultPhysicalFileFactory.class.getName() + ".readAheadCaching"; 067 068 /** 069 * Constant for the name of attribute {@code readAheadCacheSize}. 070 * <p> 071 * The {@code readAheadCacheSize} attribute is used to specify the 072 * size of the read-ahead cache. Its expected value is of type 073 * {@code Integer}. 074 * </p> 075 */ 076 public static final String ATTRIBUTE_READAHEAD_CACHESIZE = 077 DefaultPhysicalFileFactory.class.getName() + ".readAheadCacheSize"; 078 079 /** 080 * Constant for the name of attribute {@code coalescingCaching}. 081 * <p> 082 * The {@code coalescingCaching} attribute is used to enabled or disable the 083 * use of a coalescing caching algorithm. Its expected value is of type 084 * {@code Boolean}. 085 * </p> 086 */ 087 public static final String ATTRIBUTE_COALESCING_CACHING = 088 DefaultPhysicalFileFactory.class.getName() + ".coalescingCaching"; 089 090 /** 091 * Constant for the name of attribute {@code coalescingBlockSize}. 092 * <p> 093 * The {@code coalescingBlockSize} attribute is used to specify the 094 * value of property {@code blockSize} to use when constructing the 095 * coalescing cache implementation. Its expected value is of type 096 * {@code Integer}. 097 * </p> 098 */ 099 public static final String ATTRIBUTE_COALESCING_BLOCKSIZE = 100 DefaultPhysicalFileFactory.class.getName() + ".coalescingBlockSize"; 101 102 /** 103 * Constant for the name of attribute {@code spaceCharactersAllowed}. 104 * <p> 105 * The {@code spaceCharactersAllowed} attribute is used to specify numeric 106 * fields for which space characters are to be allowed. It is used as 107 * a prefix with the hexadecimal field constant appended. Its expected value 108 * is of type {@code Boolean}. 109 * </p> 110 */ 111 public static final String ATTRIBUTE_SPACE_CHARACTERS_ALLOWED = 112 DefaultPhysicalFileFactory.class.getName() + ".spaceCharactersAllowed."; 113 114 /** Implementation meta-data. */ 115 private Implementation implementation; 116 117 public int analyse( final File file ) throws PhysicalFileException, IOException 118 { 119 if ( file == null ) 120 { 121 throw new NullPointerException( "file" ); 122 } 123 124 this.assertValidProperties(); 125 final FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "r" ) ); 126 final int format = this.analyse( ops ); 127 ops.close(); 128 return format; 129 } 130 131 public int analyse( final FileOperations fileOperations ) throws PhysicalFileException, IOException 132 { 133 int blockSize = 128; 134 long remainder = 0; 135 int read = 0; 136 int ret = FORMAT_DISK; 137 int total = 0; 138 139 final Message[] messages; 140 final byte[] buf = new byte[ 4 ]; 141 final String str; 142 final long length; 143 144 if ( fileOperations == null ) 145 { 146 throw new NullPointerException( "fileOperations" ); 147 } 148 149 this.assertValidProperties(); 150 length = fileOperations.getLength(); 151 try 152 { 153 ThreadLocalMessages.getMessages().clear(); 154 ThreadLocalMessages.setErrorsEnabled( false ); 155 156 if ( length >= 128 ) 157 { // mindestens ein Disketten-Satzabschnitt. 158 // die ersten 4 Byte lesen. 159 fileOperations.setFilePointer( 0L ); 160 do 161 { 162 read = fileOperations.read( buf, total, buf.length - total ); 163 if ( read == FileOperations.EOF ) 164 { 165 throw new EOFException(); 166 } 167 else 168 { 169 total += read; 170 } 171 } 172 while ( total < buf.length ); 173 174 // Diskettenformat prüfen "0128". 175 str = Charsets.decode( buf, "ISO646-DE" ); 176 if ( "0128".equals( str ) ) 177 { 178 remainder = length % blockSize; 179 } 180 else 181 { 182 final int size = ( ( buf[0] & 0xFF ) << 8 ) | ( buf[1] & 0xFF ); 183 if ( size == 150 ) 184 { 185 ret = FORMAT_TAPE; 186 blockSize = 150; 187 remainder = 0; // Variable blocksize. 188 } 189 else 190 { 191 if ( ThreadLocalMessages.isErrorsEnabled() ) 192 { 193 throw new CorruptedException( this.getImplementation(), 0L ); 194 } 195 else 196 { 197 final Message msg = new IllegalDataMessage( 198 Fields.FIELD_A1, IllegalDataMessage.TYPE_CONSTANT, 0L, str ); 199 200 ThreadLocalMessages.getMessages().addMessage( msg ); 201 } 202 } 203 } 204 } 205 else 206 { 207 if ( ThreadLocalMessages.isErrorsEnabled() ) 208 { 209 throw new CorruptedException( this.getImplementation(), length ); 210 } 211 else 212 { 213 final Message msg = new IllegalFileLengthMessage( length, blockSize ); 214 ThreadLocalMessages.getMessages().addMessage( msg ); 215 } 216 } 217 218 if ( remainder > 0 ) 219 { 220 if ( ThreadLocalMessages.isErrorsEnabled() ) 221 { 222 throw new CorruptedException( this.getImplementation(), length ); 223 } 224 else 225 { 226 final Message msg = new IllegalFileLengthMessage( length, blockSize ); 227 ThreadLocalMessages.getMessages().addMessage( msg ); 228 } 229 } 230 231 messages = ThreadLocalMessages.getMessages().getMessages(); 232 if ( messages.length > 0 ) 233 { 234 throw new PhysicalFileException( messages ); 235 } 236 237 return ret; 238 } 239 finally 240 { 241 ThreadLocalMessages.setErrorsEnabled( true ); 242 } 243 } 244 245 public PhysicalFile createPhysicalFile( final File file, final int format ) 246 throws IOException 247 { 248 return this.createPhysicalFile( file, format, this.getDefaultProperties() ); 249 } 250 251 public PhysicalFile createPhysicalFile( final File file, final int format, final java.util.Properties properties ) 252 throws IOException 253 { 254 if ( file == null ) 255 { 256 throw new NullPointerException( "file" ); 257 } 258 259 this.assertValidProperties(); 260 this.assertValidProperties( properties ); 261 262 FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "rw" ) ); 263 ops = this.configureCoalescingCaching( ops, properties ); 264 return this.createPhysicalFile( ops, format, properties ); 265 } 266 267 public PhysicalFile createPhysicalFile( 268 final FileOperations ops, final int format ) throws IOException 269 { 270 return this.createPhysicalFile( ops, format, 271 this.getDefaultProperties() ); 272 273 } 274 275 public PhysicalFile createPhysicalFile( FileOperations ops, final int format, final java.util.Properties properties ) 276 throws IOException 277 { 278 if ( ops == null ) 279 { 280 throw new NullPointerException( "ops" ); 281 } 282 if ( format != FORMAT_DISK && format != FORMAT_TAPE ) 283 { 284 throw new IllegalArgumentException( Integer.toString( format ) ); 285 } 286 287 this.assertValidProperties(); 288 this.assertValidProperties( properties ); 289 290 try 291 { 292 ops.setLength( 0L ); 293 return this.getPhysicalFile( ops, format, properties ); 294 } 295 catch ( PhysicalFileException e ) 296 { 297 throw new AssertionError( e ); 298 } 299 } 300 301 public PhysicalFile getPhysicalFile( final File file ) throws PhysicalFileException, IOException 302 { 303 return this.getPhysicalFile( file, this.getDefaultProperties() ); 304 } 305 306 public PhysicalFile getPhysicalFile( final FileOperations ops ) throws PhysicalFileException, IOException 307 { 308 return this.getPhysicalFile( ops, this.getDefaultProperties() ); 309 } 310 311 public PhysicalFile getPhysicalFile( final FileOperations ops, final java.util.Properties properties ) 312 throws PhysicalFileException, IOException 313 { 314 if ( ops == null ) 315 { 316 throw new NullPointerException( "ops" ); 317 } 318 319 this.assertValidProperties(); 320 this.assertValidProperties( properties ); 321 return this.getPhysicalFile( ops, this.getDefaultFormat(), properties ); 322 } 323 324 public PhysicalFile getPhysicalFile( final File file, final java.util.Properties properties ) 325 throws PhysicalFileException, IOException 326 { 327 if ( file == null ) 328 { 329 throw new NullPointerException( "file" ); 330 } 331 332 this.assertValidProperties(); 333 this.assertValidProperties( properties ); 334 335 FileOperations ops = new RandomAccessFileOperations( new RandomAccessFile( file, "rw" ) ); 336 ops = this.configureReadAheadCaching( ops, properties ); 337 return this.getPhysicalFile( ops, properties ); 338 } 339 340 /** 341 * Checks configured properties. 342 * 343 * @throws PropertyException for illegal property values. 344 */ 345 private void assertValidProperties() 346 { 347 final int defaultFormat = this.getDefaultFormat(); 348 if ( defaultFormat != FORMAT_DISK && defaultFormat != FORMAT_TAPE ) 349 { 350 throw new PropertyException( "defaultFormat", new Integer( defaultFormat ) ); 351 } 352 } 353 354 /** 355 * Checks that given properties are valid. 356 * 357 * @param properties the properties to check. 358 * 359 * @throws NullPointerException if {@code properties} is {@code null}. 360 * @throws IllegalArgumentException if {@code properties} holds invalid values. 361 */ 362 private void assertValidProperties( final java.util.Properties properties ) 363 { 364 if ( properties == null ) 365 { 366 throw new NullPointerException( "properties" ); 367 } 368 369 for ( Iterator it = properties.entrySet().iterator(); it.hasNext(); ) 370 { 371 final Map.Entry entry = (Map.Entry) it.next(); 372 final String name = (String) entry.getKey(); 373 final String value = (String) entry.getValue(); 374 375 if ( name.startsWith( ATTRIBUTE_SPACE_CHARACTERS_ALLOWED ) ) 376 { 377 try 378 { 379 Integer.parseInt( name.substring( name.lastIndexOf( '.' ) + 1 ), 16 ); 380 } 381 catch ( NumberFormatException e ) 382 { 383 throw (IllegalArgumentException) new IllegalArgumentException( 384 name + ": " + e.getMessage() ).initCause( e ); 385 386 } 387 } 388 389 if ( value != null && ( ATTRIBUTE_READAHEAD_CACHESIZE.equals( name ) || 390 ATTRIBUTE_COALESCING_BLOCKSIZE.equals( name ) ) ) 391 { 392 try 393 { 394 Integer.parseInt( value ); 395 } 396 catch ( NumberFormatException e ) 397 { 398 throw (IllegalArgumentException) new IllegalArgumentException( this.getIllegalAttributeTypeMessage( 399 this.getLocale(), name, ( value != null ? value.getClass().getName() : null ), 400 Integer.class.getName() ) ).initCause( e ); 401 402 } 403 } 404 } 405 } 406 407 private java.util.Properties getDefaultProperties() 408 { 409 final java.util.Properties properties = new java.util.Properties(); 410 properties.setProperty( ATTRIBUTE_READAHEAD_CACHING, Boolean.toString( true ) ); 411 properties.setProperty( ATTRIBUTE_COALESCING_CACHING, Boolean.toString( true ) ); 412 return properties; 413 } 414 415 private FileOperations configureReadAheadCaching( FileOperations ops, final java.util.Properties properties ) 416 throws IOException 417 { 418 final String readAheadCaching = properties.getProperty( ATTRIBUTE_READAHEAD_CACHING ); 419 final String readAheadCacheSize = properties.getProperty( ATTRIBUTE_READAHEAD_CACHESIZE ); 420 final boolean isReadAheadCaching = 421 readAheadCaching != null && Boolean.valueOf( readAheadCaching ).booleanValue(); 422 423 if ( isReadAheadCaching ) 424 { 425 if ( readAheadCacheSize != null ) 426 { 427 ops = new ReadAheadFileOperations( ops, Integer.parseInt( readAheadCacheSize ) ); 428 } 429 else 430 { 431 ops = new ReadAheadFileOperations( ops ); 432 } 433 } 434 435 return ops; 436 } 437 438 private FileOperations configureCoalescingCaching( FileOperations ops, final java.util.Properties properties ) 439 throws IOException 440 { 441 final String coalescingCaching = properties.getProperty( ATTRIBUTE_COALESCING_CACHING ); 442 final String coalescingBlockSize = properties.getProperty( ATTRIBUTE_COALESCING_BLOCKSIZE ); 443 final boolean isCoalescingCaching = 444 coalescingCaching != null && Boolean.valueOf( coalescingCaching ).booleanValue(); 445 446 if ( isCoalescingCaching ) 447 { 448 if ( coalescingBlockSize != null ) 449 { 450 ops = new CoalescingFileOperations( ops, Integer.parseInt( coalescingBlockSize ) ); 451 } 452 else 453 { 454 ops = new CoalescingFileOperations( ops ); 455 } 456 } 457 458 return ops; 459 } 460 461 private PhysicalFile getPhysicalFile( final FileOperations ops, int format, final java.util.Properties properties ) 462 throws PhysicalFileException, IOException 463 { 464 if ( ops == null ) 465 { 466 throw new NullPointerException( "ops" ); 467 } 468 if ( format != FORMAT_DISK && format != FORMAT_TAPE ) 469 { 470 throw new IllegalArgumentException( Integer.toString( format ) ); 471 } 472 473 this.assertValidProperties( properties ); 474 475 final DefaultPhysicalFile ret; 476 final Message[] messages; 477 format = ops.getLength() > 0 ? this.analyse( ops ) : format; 478 479 try 480 { 481 ThreadLocalMessages.getMessages().clear(); 482 ThreadLocalMessages.setErrorsEnabled( false ); 483 ret = new DefaultPhysicalFile( format, ops, properties ); 484 messages = ThreadLocalMessages.getMessages().getMessages(); 485 if ( messages.length > 0 ) 486 { 487 throw new PhysicalFileException( messages ); 488 } 489 490 return ret; 491 } 492 finally 493 { 494 ThreadLocalMessages.setErrorsEnabled( true ); 495 } 496 } 497 498 protected Implementation getImplementation() 499 { 500 if ( this.implementation == null ) 501 { 502 this.implementation = ModelFactory.getModel().getModules(). 503 getImplementation( DefaultPhysicalFileFactory.class.getName() ); 504 505 } 506 507 return this.implementation; 508 } 509 510 //--Constructors------------------------------------------------------------ 511 512// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausConstructors 513 // This section is managed by jdtaus-container-mojo. 514 515 /** Standard implementation constructor <code>org.jdtaus.banking.dtaus.ri.zka.DefaultPhysicalFileFactory</code>. */ 516 public DefaultPhysicalFileFactory() 517 { 518 super(); 519 } 520 521// </editor-fold>//GEN-END:jdtausConstructors 522 523 //------------------------------------------------------------Constructors-- 524 //--Properties-------------------------------------------------------------- 525 526// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausProperties 527 // This section is managed by jdtaus-container-mojo. 528 529 /** 530 * Gets the value of property <code>defaultFormat</code>. 531 * 532 * @return The format to use for empty files. 533 */ 534 private int getDefaultFormat() 535 { 536 return ( (java.lang.Integer) ContainerFactory.getContainer(). 537 getProperty( this, "defaultFormat" ) ).intValue(); 538 539 } 540 541// </editor-fold>//GEN-END:jdtausProperties 542 543 //--------------------------------------------------------------Properties-- 544 //--Dependencies------------------------------------------------------------ 545 546// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausDependencies 547 // This section is managed by jdtaus-container-mojo. 548 549 /** 550 * Gets the configured <code>Locale</code> implementation. 551 * 552 * @return The configured <code>Locale</code> implementation. 553 */ 554 private Locale getLocale() 555 { 556 return (Locale) ContainerFactory.getContainer(). 557 getDependency( this, "Locale" ); 558 559 } 560 561// </editor-fold>//GEN-END:jdtausDependencies 562 563 //------------------------------------------------------------Dependencies-- 564 //--Messages---------------------------------------------------------------- 565 566// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:jdtausMessages 567 // This section is managed by jdtaus-container-mojo. 568 569 /** 570 * Gets the text of message <code>illegalAttributeType</code>. 571 * <blockquote><pre>Ungültiger Attribut-Typ {1} für Attribut {0}. Erwartet Typ {2}.</pre></blockquote> 572 * <blockquote><pre>The type {1} for attribute {0} is invalid. Expected {2}.</pre></blockquote> 573 * 574 * @param locale The locale of the message instance to return. 575 * @param attributeName format parameter. 576 * @param typeName format parameter. 577 * @param expectedTypeName format parameter. 578 * 579 * @return the text of message <code>illegalAttributeType</code>. 580 */ 581 private String getIllegalAttributeTypeMessage( final Locale locale, 582 final java.lang.String attributeName, 583 final java.lang.String typeName, 584 final java.lang.String expectedTypeName ) 585 { 586 return ContainerFactory.getContainer(). 587 getMessage( this, "illegalAttributeType", locale, 588 new Object[] 589 { 590 attributeName, 591 typeName, 592 expectedTypeName 593 }); 594 595 } 596 597// </editor-fold>//GEN-END:jdtausMessages 598 599 //----------------------------------------------------------------Messages-- 600}