001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.iso; 020 021import org.jpos.iso.header.BaseHeader; 022import org.jpos.iso.packager.XMLPackager; 023import org.jpos.util.Loggeable; 024 025import java.io.*; 026import java.lang.ref.WeakReference; 027import java.util.*; 028 029/** 030 * implements <b>Composite</b> 031 * within a <b>Composite pattern</b> 032 * 033 * @author apr@cs.com.uy 034 * @version $Id$ 035 * @see ISOComponent 036 * @see ISOField 037 */ 038@SuppressWarnings("unchecked") 039public class ISOMsg extends ISOComponent 040 implements Cloneable, Loggeable, Externalizable 041{ 042 protected Map<Integer,Object> fields; 043 protected int maxField; 044 protected ISOPackager packager; 045 protected boolean dirty, maxFieldDirty; 046 protected int direction; 047 protected ISOHeader header; 048 protected byte[] trailer; 049 protected int fieldNumber = -1; 050 public static final int INCOMING = 1; 051 public static final int OUTGOING = 2; 052 private static final long serialVersionUID = 4306251831901413975L; 053 private WeakReference sourceRef; 054 055 /** 056 * Creates an ISOMsg 057 */ 058 public ISOMsg () { 059 fields = new TreeMap<>(); 060 maxField = -1; 061 dirty = true; 062 maxFieldDirty=true; 063 direction = 0; 064 header = null; 065 trailer = null; 066 } 067 /** 068 * Creates a nested ISOMsg 069 * @param fieldNumber (in the outer ISOMsg) of this nested message 070 */ 071 public ISOMsg (int fieldNumber) { 072 this(); 073 setFieldNumber (fieldNumber); 074 } 075 /** 076 * changes this Component field number<br> 077 * Use with care, this method does not change 078 * any reference held by a Composite. 079 * @param fieldNumber new field number 080 */ 081 @Override 082 public void setFieldNumber (int fieldNumber) { 083 this.fieldNumber = fieldNumber; 084 } 085 /** 086 * Creates an ISOMsg with given mti 087 * @param mti Msg's MTI 088 */ 089 @SuppressWarnings("PMD.EmptyCatchBlock") 090 public ISOMsg (String mti) { 091 this(); 092 try { 093 setMTI (mti); 094 } catch (ISOException ignored) { 095 // Should never happen as this is not an inner message 096 } 097 } 098 /** 099 * Sets the direction information related to this message 100 * @param direction can be either ISOMsg.INCOMING or ISOMsg.OUTGOING 101 */ 102 public void setDirection(int direction) { 103 this.direction = direction; 104 } 105 /** 106 * Sets an optional message header image 107 * @param b header image 108 */ 109 public void setHeader(byte[] b) { 110 header = new BaseHeader (b); 111 } 112 113 public void setHeader (ISOHeader header) { 114 this.header = header; 115 } 116 /** 117 * get optional message header image 118 * @return message header image (may be null) 119 */ 120 public byte[] getHeader() { 121 return header != null ? header.pack() : null; 122 } 123 124 /** 125 * Sets optional trailer data. 126 * <p/> 127 * Note: The trailer data requires a customised channel that explicitly handles the trailer data from the ISOMsg. 128 * 129 * @param trailer The trailer data. 130 * @see BaseChannel#getMessageTrailer(ISOMsg). 131 * @see BaseChannel#sendMessageTrailer(ISOMsg, byte[]). 132 */ 133 public void setTrailer(byte[] trailer) { 134 this.trailer = trailer; 135 } 136 137 /** 138 * Get optional trailer image. 139 * 140 * @return message trailer image (may be null) 141 */ 142 public byte[] getTrailer() { 143 return this.trailer; 144 } 145 146 /** 147 * Return this messages ISOHeader 148 * @return header associated with this ISOMsg, can be null 149 */ 150 public ISOHeader getISOHeader() { 151 return header; 152 } 153 /** 154 * @return the direction (ISOMsg.INCOMING or ISOMsg.OUTGOING) 155 * @see ISOChannel 156 */ 157 public int getDirection() { 158 return direction; 159 } 160 /** 161 * @return true if this message is an incoming message 162 * @see ISOChannel 163 */ 164 public boolean isIncoming() { 165 return direction == INCOMING; 166 } 167 /** 168 * @return true if this message is an outgoing message 169 * @see ISOChannel 170 */ 171 public boolean isOutgoing() { 172 return direction == OUTGOING; 173 } 174 /** 175 * @return the max field number associated with this message 176 */ 177 @Override 178 public int getMaxField() { 179 if (maxFieldDirty) 180 recalcMaxField(); 181 return maxField; 182 } 183 private void recalcMaxField() { 184 maxField = 0; 185 for (Object obj : fields.keySet()) { 186 if (obj instanceof Integer) 187 maxField = Math.max(maxField, ((Integer) obj).intValue()); 188 } 189 maxFieldDirty = false; 190 } 191 /** 192 * @param p - a peer packager 193 */ 194 public void setPackager (ISOPackager p) { 195 packager = p; 196 if (packager == null) { 197 for (Object o : fields.values()) { 198 if (o instanceof ISOMsg) 199 ((ISOMsg) o).setPackager(null); 200 } 201 } 202 } 203 /** 204 * @return the peer packager 205 */ 206 public ISOPackager getPackager () { 207 return packager; 208 } 209 /** 210 * Set a field within this message 211 * @param c - a component 212 */ 213 public void set (ISOComponent c) throws ISOException { 214 if (c != null) { 215 Integer i = (Integer) c.getKey(); 216 fields.put (i, c); 217 if (i > maxField) 218 maxField = i; 219 dirty = true; 220 } 221 } 222 223 /** 224 * Creates an ISOField associated with fldno within this ISOMsg. 225 * 226 * @param fldno field number 227 * @param value field value 228 */ 229 public void set(int fldno, String value) { 230 if (value == null) { 231 unset(fldno); 232 return; 233 } 234 235 try { 236 if (!(packager instanceof ISOBasePackager)) { 237 // No packager is available, we can't tell what the field 238 // might be, so treat as a String! 239 set(new ISOField(fldno, value)); 240 } 241 else { 242 // This ISOMsg has a packager, so use it 243 Object obj = ((ISOBasePackager) packager).getFieldPackager(fldno); 244 if (obj instanceof ISOBinaryFieldPackager) { 245 set(new ISOBinaryField(fldno, ISOUtil.hex2byte(value))); 246 } else { 247 set(new ISOField(fldno, value)); 248 } 249 } 250 } catch (ISOException ex) {}; //NOPMD: never happens for the given arguments of set methods 251 } 252 253 /** 254 * Creates an ISOField associated with fldno within this ISOMsg. 255 * 256 * @param fpath dot-separated field path (i.e. 63.2) 257 * @param value field value 258 */ 259 public void set(String fpath, String value) { 260 StringTokenizer st = new StringTokenizer (fpath, "."); 261 ISOMsg m = this; 262 for (;;) { 263 int fldno = parseInt(st.nextToken()); 264 if (st.hasMoreTokens()) { 265 Object obj = m.getValue(fldno); 266 if (obj instanceof ISOMsg) 267 m = (ISOMsg) obj; 268 else 269 /** 270 * we need to go deeper, however, if the value == null then 271 * there is nothing to do (unset) at the lower levels, so break now and save some processing. 272 */ 273 if (value == null) { 274 break; 275 } else { 276 try { 277 // We have a value to set, so adding a level to hold it is sensible. 278 m.set(m = new ISOMsg (fldno)); 279 } catch (ISOException ex) {} //NOPMD: never happens for the given arguments of set methods 280 } 281 } else { 282 m.set(fldno, value); 283 break; 284 } 285 } 286 } 287 288 /** 289 * Creates an ISOField associated with fldno within this ISOMsg 290 * @param fpath dot-separated field path (i.e. 63.2) 291 * @param c component 292 * @throws ISOException on error 293 */ 294 public void set(String fpath, ISOComponent c) throws ISOException { 295 StringTokenizer st = new StringTokenizer (fpath, "."); 296 ISOMsg m = this; 297 for (;;) { 298 int fldno = parseInt(st.nextToken()); 299 if (st.hasMoreTokens()) { 300 Object obj = m.getValue(fldno); 301 if (obj instanceof ISOMsg) 302 m = (ISOMsg) obj; 303 else 304 /* 305 * we need to go deeper, however, if the value == null then 306 * there is nothing to do (unset) at the lower levels, so break now and save some processing. 307 */ 308 if (c == null) { 309 break; 310 } else { 311 // We have a value to set, so adding a level to hold it is sensible. 312 m.set(m = new ISOMsg(fldno)); 313 } 314 } else { 315 if (c != null) 316 c.setFieldNumber(fldno); 317 m.set(c); 318 break; 319 } 320 } 321 } 322 /** 323 * Creates an ISOField associated with fldno within this ISOMsg. 324 * 325 * @param fpath dot-separated field path (i.e. 63.2) 326 * @param value binary field value 327 */ 328 public void set(String fpath, byte[] value) { 329 StringTokenizer st = new StringTokenizer (fpath, "."); 330 ISOMsg m = this; 331 for (;;) { 332 int fldno = parseInt(st.nextToken()); 333 if (st.hasMoreTokens()) { 334 Object obj = m.getValue(fldno); 335 if (obj instanceof ISOMsg) 336 m = (ISOMsg) obj; 337 else 338 try { 339 m.set(m = new ISOMsg (fldno)); 340 } catch (ISOException ex) {} //NOPMD: never happens for the given arguments of set methods 341 } else { 342 m.set(fldno, value); 343 break; 344 } 345 } 346 } 347 348 /** 349 * Creates an ISOBinaryField associated with fldno within this ISOMsg. 350 * 351 * @param fldno field number 352 * @param value field value 353 */ 354 public void set(int fldno, byte[] value) { 355 if (value == null) { 356 unset(fldno); 357 return; 358 } 359 360 try { 361 set(new ISOBinaryField(fldno, value)); 362 } catch (ISOException ex) {}; //NOPMD: never happens for the given arguments of set methods 363 } 364 365 366 /** 367 * Unset a field if it exists, otherwise ignore. 368 * @param fldno - the field number 369 */ 370 @Override 371 public void unset (int fldno) { 372 if (fields.remove (fldno) != null) 373 dirty = maxFieldDirty = true; 374 } 375 376 /** 377 * Unsets several fields at once 378 * @param flds - array of fields to be unset from this ISOMsg 379 */ 380 public void unset (int ... flds) { 381 for (int fld : flds) 382 unset(fld); 383 } 384 385 /** 386 * Unset a field referenced by a fpath if it exists, otherwise ignore. 387 * 388 * @param fpath dot-separated field path (i.e. 63.2) 389 */ 390 public void unset(String fpath) { 391 StringTokenizer st = new StringTokenizer (fpath, "."); 392 ISOMsg m = this; 393 ISOMsg lastm = m; 394 int fldno = -1 ; 395 int lastfldno ; 396 for (;;) { 397 lastfldno = fldno; 398 fldno = parseInt(st.nextToken()); 399 if (st.hasMoreTokens()) { 400 Object obj = m.getValue(fldno); 401 if (obj instanceof ISOMsg) { 402 lastm = m; 403 m = (ISOMsg) obj; 404 } 405 else { 406 // No real way of unset further subfield, exit. 407 break; 408 } 409 } else { 410 m.unset(fldno); 411 if (!m.hasFields() && lastfldno != -1) { 412 lastm.unset(lastfldno); 413 } 414 break; 415 } 416 } 417 } 418 419 /** 420 * Unset a a set of fields referenced by fpaths if any ot them exist, otherwise ignore. 421 * 422 * @param fpaths dot-separated field paths (i.e. 63.2) 423 */ 424 public void unset(String ... fpaths) { 425 for (String fpath : fpaths) { 426 unset(fpath); 427 } 428 } 429 /** 430 * In order to interchange <b>Composites</b> and <b>Leafs</b> we use 431 * getComposite(). A <b>Composite component</b> returns itself and 432 * a Leaf returns null. 433 * 434 * @return ISOComponent 435 */ 436 @Override 437 public ISOComponent getComposite() { 438 return this; 439 } 440 /** 441 * setup BitMap 442 * @exception ISOException on error 443 */ 444 public void recalcBitMap () throws ISOException { 445 if (!dirty) 446 return; 447 448 int mf = Math.min (getMaxField(), 192); 449 450 BitSet bmap = new BitSet (mf+62 >>6 <<6); 451 for (int i=1; i<=mf; i++) 452 if (fields.get (i) != null) 453 bmap.set (i); 454 set (new ISOBitMap (-1, bmap)); 455 dirty = false; 456 } 457 /** 458 * clone fields 459 * @return copy of fields 460 */ 461 @Override 462 public Map getChildren() { 463 return (Map) ((TreeMap)fields).clone(); 464 } 465 /** 466 * pack the message with the current packager 467 * @return the packed message 468 * @exception ISOException 469 */ 470 @Override 471 public byte[] pack() throws ISOException { 472 synchronized (this) { 473 recalcBitMap(); 474 return packager.pack(this); 475 } 476 } 477 /** 478 * unpack a message 479 * @param b - raw message 480 * @return consumed bytes 481 * @exception ISOException 482 */ 483 @Override 484 public int unpack(byte[] b) throws ISOException { 485 synchronized (this) { 486 return packager.unpack(this, b); 487 } 488 } 489 @Override 490 public void unpack (InputStream in) throws IOException, ISOException { 491 synchronized (this) { 492 packager.unpack(this, in); 493 } 494 } 495 /** 496 * dump the message to a PrintStream. The output is sorta 497 * XML, intended to be easily parsed. 498 * <br> 499 * Each component is responsible for its own dump function, 500 * ISOMsg just calls dump on every valid field. 501 * @param p - print stream 502 * @param indent - optional indent string 503 */ 504 @Override 505 public void dump (PrintStream p, String indent) { 506 ISOComponent c; 507 p.print (indent + "<" + XMLPackager.ISOMSG_TAG); 508 switch (direction) { 509 case INCOMING: 510 p.print (" direction=\"incoming\""); 511 break; 512 case OUTGOING: 513 p.print (" direction=\"outgoing\""); 514 break; 515 } 516 if (fieldNumber != -1) 517 p.print (" "+XMLPackager.ID_ATTR +"=\""+fieldNumber +"\""); 518 p.println (">"); 519 String newIndent = indent + " "; 520 if (getPackager() != null) { 521 p.println ( 522 newIndent 523 + "<!-- " + getPackager().getDescription() + " -->" 524 ); 525 } 526 if (header instanceof Loggeable) 527 ((Loggeable) header).dump (p, newIndent); 528 529 for (int i : fields.keySet()) { 530 //If you want the bitmap dumped in the log, change the condition from (i >= 0) to (i >= -1). 531 if (i >= 0) { 532 if ((c = (ISOComponent) fields.get(i)) != null) 533 c.dump(p, newIndent); 534 } 535 } 536 537 p.println (indent + "</" + XMLPackager.ISOMSG_TAG+">"); 538 } 539 /** 540 * get the component associated with the given field number 541 * @param fldno the Field Number 542 * @return the Component 543 */ 544 public ISOComponent getComponent(int fldno) { 545 return (ISOComponent) fields.get(fldno); 546 } 547 /** 548 * Return the object value associated with the given field number 549 * @param fldno the Field Number 550 * @return the field Object 551 */ 552 public Object getValue(int fldno) { 553 ISOComponent c = getComponent(fldno); 554 try { 555 return c != null ? c.getValue() : null; 556 } catch (ISOException ex) { 557 return null; //never happens for the given arguments of getValue method 558 } 559 } 560 /** 561 * Return the object value associated with the given field path 562 * @param fpath field path 563 * @return the field Object (may be null) 564 * @throws ISOException on error 565 */ 566 public Object getValue (String fpath) throws ISOException { 567 StringTokenizer st = new StringTokenizer (fpath, "."); 568 ISOMsg m = this; 569 Object obj; 570 for (;;) { 571 int fldno = parseInt(st.nextToken()); 572 obj = m.getValue (fldno); 573 if (obj==null){ 574 // The user will always get a null value for an incorrect path or path not present in the message 575 // no point having the ISOException thrown for fields that were not received. 576 break; 577 } 578 if (st.hasMoreTokens()) { 579 if (obj instanceof ISOMsg) { 580 m = (ISOMsg) obj; 581 } 582 else 583 throw new ISOException ("Invalid path '" + fpath + "'"); 584 } else 585 break; 586 } 587 return obj; 588 } 589 /** 590 * get the component associated with the given field number 591 * @param fpath field path 592 * @return the Component 593 * @throws ISOException on error 594 */ 595 public ISOComponent getComponent (String fpath) throws ISOException { 596 StringTokenizer st = new StringTokenizer (fpath, "."); 597 ISOMsg m = this; 598 ISOComponent obj; 599 for (;;) { 600 int fldno = parseInt(st.nextToken()); 601 obj = m.getComponent(fldno); 602 if (st.hasMoreTokens()) { 603 if (obj instanceof ISOMsg) { 604 m = (ISOMsg) obj; 605 } 606 else 607 break; // 'Quick' exit if hierarchy is not present. 608 } else 609 break; 610 } 611 return obj; 612 } 613 /** 614 * Return the String value associated with the given ISOField number 615 * @param fldno the Field Number 616 * @return field's String value 617 */ 618 public String getString (int fldno) { 619 String s = null; 620 if (hasField (fldno)) { 621 Object obj = getValue(fldno); 622 if (obj instanceof String) 623 s = (String) obj; 624 else if (obj instanceof byte[]) 625 s = ISOUtil.hexString((byte[]) obj); 626 } 627 return s; 628 } 629 /** 630 * Return the String value associated with the given field path 631 * @param fpath field path 632 * @return field's String value (may be null) 633 */ 634 public String getString (String fpath) { 635 String s = null; 636 try { 637 Object obj = getValue(fpath); 638 if (obj instanceof String) 639 s = (String) obj; 640 else if (obj instanceof byte[]) 641 s = ISOUtil.hexString ((byte[]) obj); 642 } catch (ISOException e) { 643 return null; 644 } 645 return s; 646 } 647 /** 648 * Return the byte[] value associated with the given ISOField number 649 * @param fldno the Field Number 650 * @return field's byte[] value or null if ISOException or UnsupportedEncodingException happens 651 */ 652 public byte[] getBytes (int fldno) { 653 byte[] b = null; 654 if (hasField (fldno)) { 655 Object obj = getValue(fldno); 656 if (obj instanceof String) 657 b = ((String) obj).getBytes(ISOUtil.CHARSET); 658 else if (obj instanceof byte[]) 659 b = (byte[]) obj; 660 } 661 return b; 662 } 663 /** 664 * Return the String value associated with the given field path 665 * @param fpath field path 666 * @return field's byte[] value (may be null) 667 */ 668 public byte[] getBytes (String fpath) { 669 byte[] b = null; 670 try { 671 Object obj = getValue(fpath); 672 if (obj instanceof String) 673 b = ((String) obj).getBytes(ISOUtil.CHARSET); 674 else if (obj instanceof byte[]) 675 b = (byte[]) obj; 676 } catch (ISOException ignored) { 677 return null; 678 } 679 return b; 680 } 681 /** 682 * Check if a given field is present 683 * @param fldno the Field Number 684 * @return boolean indicating the existence of the field 685 */ 686 public boolean hasField(int fldno) { 687 return fields.get(fldno) != null; 688 } 689 /** 690 * Check if all fields are present 691 * @param fields an array of fields to check for presence 692 * @return true if all fields are present 693 */ 694 public boolean hasFields (int[] fields) { 695 for (int field : fields) 696 if (!hasField(field)) 697 return false; 698 return true; 699 } 700 701 /** 702 * Check if the message has any of these fields 703 * @param fields an array of fields to check for presence 704 * @return true if at least one field is present 705 */ 706 public boolean hasAny (int[] fields) { 707 for (int field : fields) 708 if (hasField(field)) 709 return true; 710 return false; 711 } 712 /** 713 * Check if the message has any of these fields 714 * @param fields to check for presence 715 * @return true if at least one field is present 716 */ 717 public boolean hasAny (String... fields) { 718 for (String field : fields) 719 if (hasField (field)) 720 return true; 721 return false; 722 } 723 724 /** 725 * Check if a field indicated by a fpath is present 726 * @param fpath dot-separated field path (i.e. 63.2) 727 * @return true if field present 728 */ 729 public boolean hasField (String fpath) { 730 StringTokenizer st = new StringTokenizer (fpath, "."); 731 ISOMsg m = this; 732 for (;;) { 733 int fldno = parseInt(st.nextToken()); 734 if (st.hasMoreTokens()) { 735 Object obj = m.getValue(fldno); 736 if (obj instanceof ISOMsg) { 737 m = (ISOMsg) obj; 738 } 739 else { 740 // No real way of checking for further subfields, return false, perhaps should be ISOException? 741 return false; 742 } 743 } else { 744 return m.hasField(fldno); 745 } 746 } 747 } 748 /** 749 * @return true if ISOMsg has at least one field 750 */ 751 public boolean hasFields () { 752 return !fields.isEmpty(); 753 } 754 /** 755 * Don't call setValue on an ISOMsg. You'll sure get 756 * an ISOException. It's intended to be used on Leafs 757 * @param obj 758 * @throws org.jpos.iso.ISOException 759 * @see ISOField 760 * @see ISOException 761 */ 762 @Override 763 public void setValue(Object obj) throws ISOException { 764 throw new ISOException ("setValue N/A in ISOMsg"); 765 } 766 767 @Override 768 public Object clone() { 769 try { 770 ISOMsg m = (ISOMsg) super.clone(); 771 m.fields = (TreeMap) ((TreeMap) fields).clone(); 772 if (header != null) 773 m.header = (ISOHeader) header.clone(); 774 if (trailer != null) 775 m.trailer = trailer.clone(); 776 for (Integer k : fields.keySet()) { 777 ISOComponent c = (ISOComponent) m.fields.get(k); 778 if (c instanceof ISOMsg) 779 m.fields.put(k, ((ISOMsg) c).clone()); 780 } 781 return m; 782 } catch (CloneNotSupportedException e) { 783 throw new InternalError(); 784 } 785 } 786 787 /** 788 * Partially clone an ISOMsg 789 * @param fields int array of fields to go 790 * @return new ISOMsg instance 791 */ 792 @SuppressWarnings("PMD.EmptyCatchBlock") 793 public Object clone(int ... fields) { 794 try { 795 ISOMsg m = (ISOMsg) super.clone(); 796 m.fields = new TreeMap(); 797 for (int field : fields) { 798 if (hasField(field)) { 799 try { 800 ISOComponent c = getComponent(field); 801 if (c instanceof ISOMsg) { 802 m.set((ISOMsg)((ISOMsg)c).clone()); 803 } else { 804 m.set(c); 805 } 806 } catch (ISOException ignored) { 807 // should never happen 808 } 809 } 810 } 811 return m; 812 } catch (CloneNotSupportedException e) { 813 throw new InternalError(); 814 } 815 } 816 817 /** 818 * Partially clone an ISOMsg by field paths 819 * @param fpaths string array of field paths to copy 820 * @return new ISOMsg instance 821 */ 822 public ISOMsg clone(String ... fpaths) { 823 try { 824 ISOMsg m = (ISOMsg) super.clone(); 825 m.fields = new TreeMap(); 826 for (String fpath : fpaths) { 827 try { 828 ISOComponent component = getComponent(fpath); 829 if (component instanceof ISOMsg) { 830 m.set(fpath, (ISOMsg)((ISOMsg)component).clone()); 831 } else if (component != null) { 832 m.set(fpath, component); 833 } 834 } catch (ISOException ignored) { 835 //should never happen 836 } 837 } 838 return m; 839 } catch (CloneNotSupportedException e) { 840 throw new InternalError(); 841 } 842 } 843 844 /** 845 * Merges the content of the specified ISOMsg into this ISOMsg instance. 846 * It iterates over the fields of the input message and, for each field that is present, 847 * sets the corresponding component in this message to the value from the input message. 848 * This operation includes all fields that are present in the input message, but does not remove 849 * any existing fields from this message unless they are explicitly overwritten by the input message. 850 * <p> 851 * If the input message contains a header (non-null), this method also clones the header 852 * and sets it as the header of this message. 853 * 854 * @param m The ISOMsg to merge into this ISOMsg. It must not be {@code null}. 855 * The method does nothing if {@code m} is {@code null}. 856 * @param mergeHeader A boolean flag indicating whether to merge the header of the input message into this message. 857 * 858 */ 859 @SuppressWarnings("PMD.EmptyCatchBlock") 860 public void merge (ISOMsg m, boolean mergeHeader) { 861 for (int i : m.fields.keySet()) { 862 try { 863 if (i >= 0 && m.hasField(i)) 864 set(m.getComponent(i)); 865 } catch (ISOException ignored) { 866 // should never happen 867 } 868 } 869 if (mergeHeader && m.header != null) 870 header = (ISOHeader) m.header.clone(); 871 } 872 873 /* 874 * Merges the content of the specified ISOMsg into this ISOMsg instance, excluding the header. 875 * This method is a convenience wrapper around {@link #merge(ISOMsg, boolean)} with the {@code mergeHeader} 876 * parameter set to {@code false} for backward compatibility, indicating that the header of the input message 877 * will not be merged. 878 */ 879 public void merge (ISOMsg m) { 880 merge (m, false); 881 } 882 883 /** 884 * @return a string suitable for a log 885 */ 886 @Override 887 public String toString() { 888 StringBuilder s = new StringBuilder(); 889 if (isIncoming()) 890 s.append(" In: "); 891 else if (isOutgoing()) 892 s.append("Out: "); 893 else 894 s.append(" "); 895 896 s.append(getString(0)); 897 if (hasField(11)) { 898 s.append(' '); 899 s.append(getString(11)); 900 } 901 if (hasField(41)) { 902 s.append(' '); 903 s.append(getString(41)); 904 } 905 return s.toString(); 906 } 907 @Override 908 public Object getKey() throws ISOException { 909 if (fieldNumber != -1) 910 return fieldNumber; 911 throw new ISOException ("This is not a subField"); 912 } 913 @Override 914 public Object getValue() { 915 return this; 916 } 917 /** 918 * @return true on inner messages 919 */ 920 public boolean isInner() { 921 return fieldNumber > -1; 922 } 923 /** 924 * @param mti new MTI 925 * @exception ISOException if message is inner message 926 */ 927 public void setMTI (String mti) throws ISOException { 928 if (isInner()) 929 throw new ISOException ("can't setMTI on inner message"); 930 set (new ISOField (0, mti)); 931 } 932 /** 933 * moves a field (renumber) 934 * @param oldFieldNumber old field number 935 * @param newFieldNumber new field number 936 * @throws ISOException on error 937 */ 938 public void move (int oldFieldNumber, int newFieldNumber) 939 throws ISOException 940 { 941 ISOComponent c = getComponent (oldFieldNumber); 942 unset (oldFieldNumber); 943 if (c != null) { 944 c.setFieldNumber (newFieldNumber); 945 set (c); 946 } else 947 unset (newFieldNumber); 948 } 949 950 @Override 951 public int getFieldNumber () { 952 return fieldNumber; 953 } 954 955 /** 956 * @return true is message has MTI field 957 * @exception ISOException if this is an inner message 958 */ 959 public boolean hasMTI() throws ISOException { 960 if (isInner()) 961 throw new ISOException ("can't hasMTI on inner message"); 962 else 963 return hasField(0); 964 } 965 /** 966 * @return current MTI 967 * @exception ISOException on inner message or MTI not set 968 */ 969 public String getMTI() throws ISOException { 970 if (isInner()) 971 throw new ISOException ("can't getMTI on inner message"); 972 else if (!hasField(0)) 973 throw new ISOException ("MTI not available"); 974 return (String) getValue(0); 975 } 976 977 /** 978 * @return true if message "seems to be" a request 979 * @exception ISOException on MTI not set 980 */ 981 public boolean isRequest() throws ISOException { 982 return Character.getNumericValue(getMTI().charAt (2))%2 == 0; 983 } 984 /** 985 * @return true if message "seems not to be" a request 986 * @exception ISOException on MTI not set 987 */ 988 public boolean isResponse() throws ISOException { 989 return !isRequest(); 990 } 991 /** 992 * @return true if message class is "authorization" 993 * @exception ISOException on MTI not set 994 */ 995 public boolean isAuthorization() throws ISOException { 996 return hasMTI() && getMTI().charAt(1) == '1'; 997 } 998 /** 999 * @return true if message class is "financial" 1000 * @exception ISOException on MTI not set 1001 */ 1002 public boolean isFinancial() throws ISOException { 1003 return hasMTI() && getMTI().charAt(1) == '2'; 1004 } 1005 /** 1006 * @return true if message class is "file action" 1007 * @exception ISOException on MTI not set 1008 */ 1009 public boolean isFileAction() throws ISOException { 1010 return hasMTI() && getMTI().charAt(1) == '3'; 1011 } 1012 /** 1013 * @return true if message class is "reversal" 1014 * @exception ISOException on MTI not set 1015 */ 1016 public boolean isReversal() throws ISOException { 1017 return hasMTI() && getMTI().charAt(1) == '4' && (getMTI().charAt(3) == '0' || getMTI().charAt(3) == '1'); 1018 } 1019 /** 1020 * @return true if message class is "chargeback" 1021 * @exception ISOException on MTI not set 1022 */ 1023 public boolean isChargeback() throws ISOException { 1024 return hasMTI() && getMTI().charAt(1) == '4' && (getMTI().charAt(3) == '2' || getMTI().charAt(3) == '3'); 1025 } 1026 /** 1027 * @return true if message class is "reconciliation" 1028 * @exception ISOException on MTI not set 1029 */ 1030 public boolean isReconciliation() throws ISOException { 1031 return hasMTI() && getMTI().charAt(1) == '5'; 1032 } 1033 /** 1034 * @return true if message class is "administrative" 1035 * @exception ISOException on MTI not set 1036 */ 1037 public boolean isAdministrative() throws ISOException { 1038 return hasMTI() && getMTI().charAt(1) == '6'; 1039 } 1040 /** 1041 * @return true if message class is "fee collection" 1042 * @exception ISOException on MTI not set 1043 */ 1044 public boolean isFeeCollection() throws ISOException { 1045 return hasMTI() && getMTI().charAt(1) == '7'; 1046 } 1047 /** 1048 * @return true if message class is "fee collection" 1049 * @exception ISOException on MTI not set 1050 */ 1051 public boolean isNetworkManagement() throws ISOException { 1052 return hasMTI() && getMTI().charAt(1) == '8'; 1053 } 1054 /** 1055 * @return true if message is Retransmission 1056 * @exception ISOException on MTI not set 1057 */ 1058 public boolean isRetransmission() throws ISOException { 1059 return getMTI().charAt(3) == '1'; 1060 } 1061 /** 1062 * sets an appropriate response MTI. 1063 * 1064 * i.e. 0100 becomes 0110<br> 1065 * i.e. 0201 becomes 0210<br> 1066 * i.e. 1201 becomes 1210<br> 1067 * @exception ISOException on MTI not set or it is not a request 1068 */ 1069 public void setResponseMTI() throws ISOException { 1070 if (!isRequest()) 1071 throw new ISOException ("not a request - can't set response MTI"); 1072 1073 String mti = getMTI(); 1074 char c1 = mti.charAt(3); 1075 char c2 = '0'; 1076 switch (c1) 1077 { 1078 case '0' : 1079 case '1' : c2='0';break; 1080 case '2' : 1081 case '3' : c2='2';break; 1082 case '4' : 1083 case '5' : c2='4';break; 1084 1085 } 1086 set (new ISOField (0, 1087 mti.substring(0,2) 1088 +(Character.getNumericValue(getMTI().charAt (2))+1) + c2 1089 ) 1090 ); 1091 } 1092 /** 1093 * sets an appropriate retransmission MTI<br> 1094 * @exception ISOException on MTI not set or it is not a request 1095 */ 1096 public void setRetransmissionMTI() throws ISOException { 1097 if (!isRequest()) 1098 throw new ISOException ("not a request"); 1099 1100 set (new ISOField (0, getMTI().substring(0,3) + "1")); 1101 } 1102 protected void writeHeader (ObjectOutput out) throws IOException { 1103 int len = header.getLength(); 1104 if (len > 0) { 1105 out.writeByte ('H'); 1106 out.writeShort (len); 1107 out.write (header.pack()); 1108 } 1109 } 1110 1111 protected void readHeader (ObjectInput in) 1112 throws IOException, ClassNotFoundException 1113 { 1114 byte[] b = new byte[in.readShort()]; 1115 in.readFully (b); 1116 setHeader (b); 1117 } 1118 protected void writePackager(ObjectOutput out) throws IOException { 1119 out.writeByte('P'); 1120 String pclass = packager.getClass().getName(); 1121 byte[] b = pclass.getBytes(); 1122 out.writeShort(b.length); 1123 out.write(b); 1124 } 1125 protected void readPackager(ObjectInput in) throws IOException, 1126 ClassNotFoundException { 1127 byte[] b = new byte[in.readShort()]; 1128 in.readFully(b); 1129 try { 1130 Class mypClass = Class.forName(new String(b)); 1131 ISOPackager myp = (ISOPackager) mypClass.newInstance(); 1132 setPackager(myp); 1133 } catch (Exception e) { 1134 setPackager(null); 1135 } 1136 1137} 1138 protected void writeDirection (ObjectOutput out) throws IOException { 1139 out.writeByte ('D'); 1140 out.writeByte (direction); 1141 } 1142 protected void readDirection (ObjectInput in) 1143 throws IOException, ClassNotFoundException 1144 { 1145 direction = in.readByte(); 1146 } 1147 1148 @Override 1149 public void writeExternal (ObjectOutput out) throws IOException { 1150 out.writeByte (0); // reserved for future expansion (version id) 1151 out.writeShort (fieldNumber); 1152 1153 if (header != null) 1154 writeHeader (out); 1155 if (packager != null) 1156 writePackager(out); 1157 if (direction > 0) 1158 writeDirection (out); 1159 1160 // List keySet = new ArrayList (fields.keySet()); 1161 // Collections.sort (keySet); 1162 for (Object o : fields.values()) { 1163 ISOComponent c = (ISOComponent) o; 1164 if (c instanceof ISOMsg) { 1165 writeExternal(out, 'M', c); 1166 } else if (c instanceof ISOBinaryField) { 1167 writeExternal(out, 'B', c); 1168 } else if (c instanceof ISOAmount) { 1169 writeExternal(out, 'A', c); 1170 } else if (c instanceof ISOField) { 1171 writeExternal(out, 'F', c); 1172 } 1173 } 1174 out.writeByte ('E'); 1175 } 1176 1177 @Override 1178 public void readExternal (ObjectInput in) 1179 throws IOException, ClassNotFoundException 1180 { 1181 in.readByte(); // ignore version for now 1182 fieldNumber = in.readShort(); 1183 byte fieldType; 1184 ISOComponent c; 1185 try { 1186 while ((fieldType = in.readByte()) != 'E') { 1187 c = null; 1188 switch (fieldType) { 1189 case 'F': 1190 c = new ISOField (); 1191 break; 1192 case 'A': 1193 c = new ISOAmount (); 1194 break; 1195 case 'B': 1196 c = new ISOBinaryField (); 1197 break; 1198 case 'M': 1199 c = new ISOMsg (); 1200 break; 1201 case 'H': 1202 readHeader (in); 1203 break; 1204 case 'P': 1205 readPackager(in); 1206 break; 1207 case 'D': 1208 readDirection (in); 1209 break; 1210 default: 1211 throw new IOException ("malformed ISOMsg"); 1212 } 1213 if (c != null) { 1214 ((Externalizable)c).readExternal (in); 1215 set (c); 1216 } 1217 } 1218 } 1219 catch (ISOException e) { 1220 throw new IOException (e.getMessage()); 1221 } 1222 } 1223 /** 1224 * Let this ISOMsg object hold a weak reference to an ISOSource 1225 * (usually used to carry a reference to the incoming ISOChannel) 1226 * @param source an ISOSource 1227 */ 1228 public void setSource (ISOSource source) { 1229 this.sourceRef = new WeakReference (source); 1230 } 1231 /** 1232 * @return an ISOSource or null 1233 */ 1234 public ISOSource getSource () { 1235 return sourceRef != null ? (ISOSource) sourceRef.get () : null; 1236 } 1237 private void writeExternal (ObjectOutput out, char b, ISOComponent c) throws IOException { 1238 out.writeByte (b); 1239 ((Externalizable) c).writeExternal (out); 1240 } 1241 private int parseInt (String s) { 1242 return s.startsWith("0x") ? Integer.parseInt(s.substring(2), 16) : Integer.parseInt(s); 1243 } 1244} 1245