/* * Copyright 2005-2015 by BerryWorks Software, LLC. All rights reserved. * * This file is part of EDIReader. You may obtain a license for its use directly from * BerryWorks Software, and you may also choose to use this software under the terms of the * GPL version 3. Other products in the EDIReader software suite are available only by licensing * with BerryWorks. Only those files bearing the GPL statement below are available under the GPL. * * EDIReader is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * EDIReader is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with EDIReader. If not, * see <http://www.gnu.org/licenses/>. */ package com.berryworks.edireader; import com.berryworks.edireader.util.BranchingWriter; import com.berryworks.edireader.util.DateTimeGenerator; import com.berryworks.edireader.util.FixedLength; import java.io.IOException; /** * A delegate for generating an interchange containing some number of 997 * transactions acknowledging the functional groups parsed by AnsiReader. */ public class AnsiFAGenerator extends ReplyGenerator { public static final String REGEX_CHARS_NEEDING_ESCAPE = "\\.[{(*+?^$|"; public static final String REGEX_ESCAPE = "\\"; protected final BranchingWriter ackStream; protected boolean preambleGenerated, skipFA; protected String thisInterchangeControlNumber; protected final String thisGroupControlNumber; protected int thisDocumentCount; protected String referencedISA; protected boolean headerGenerated; protected boolean groupTrailerGenerated; protected char delimiter; protected String terminatorWithSuffix; private static final String CONTROL_NUMBER_997 = "0001"; public AnsiFAGenerator(final StandardReader ansiReader, final BranchingWriter ackStream) { this.standardReader = ansiReader; this.ackStream = ackStream; thisGroupControlNumber = "12345"; } @Override public void generateAcknowledgmentHeader(String firstSegment, String groupSender, String groupReceiver, int groupDateLength, String groupVersion, String groupFunctionCode, String groupControlNumber) throws IOException { if (ackStream == null) return; // Do not generate an FA to acknowledge an FA if ("FA".equals(groupFunctionCode)) { skipFA = true; return; } skipFA = false; if (EDIReader.debug) EDIReader.trace("generating FA envelope"); generateAcknowledgementPreamble(firstSegment, groupSender, groupReceiver, groupDateLength, groupVersion); // Generate the ST 997 if (EDIReader.debug) EDIReader.trace("generating first part of 997"); thisDocumentCount++; ackStream.write("ST" + delimiter + "997" + delimiter + CONTROL_NUMBER_997); ackStream.write(terminatorWithSuffix); // Generate the AK1 segment to identify the group being acknowledged ackStream.write("AK1" + delimiter + groupFunctionCode + delimiter + groupControlNumber); ackStream.write(terminatorWithSuffix); headerGenerated = true; } @Override public void generateTransactionAcknowledgment(String transactionCode, String controlNumber) throws IOException { if (ackStream == null || skipFA || standardReader.isGroupAcknowledgment()) return; generateTransactionAcknowledgmentUsing(transactionCode, controlNumber); } protected void generateTransactionAcknowledgmentUsing(String transactionCode, String controlNumber) { if (EDIReader.debug) EDIReader.trace("generating AK2/AK5"); // Generate the AK2 segment to identify the transaction set ackStream.writeTrunk("AK2" + delimiter + transactionCode + delimiter + controlNumber); ackStream.writeTrunk(terminatorWithSuffix); // Generate the AK5 segment acknowledging the transaction set ackStream.writeTrunk("AK5" + delimiter + "A"); ackStream.writeTrunk(terminatorWithSuffix); } @Override public void generateGroupAcknowledgmentTrailer(int docCount) throws IOException { if (ackStream == null || skipFA) return; if (EDIReader.debug) EDIReader.trace("generating AK9, SE"); // For the trunk, generate the AK9 segment to designate acceptance of the entire // functional group. ackStream.writeTrunk("AK9" + delimiter + "A" + delimiter + docCount + delimiter + docCount + delimiter + docCount); // For the branch, generate the AK9 segment to designate rejection of the entire // functional group. ackStream.writeBranch("AK9" + delimiter + "R" + delimiter + docCount + delimiter + docCount + delimiter + "0"); ackStream.write(terminatorWithSuffix); // Generate the SE to match the ST final int segmentCount = 4 + (standardReader.isGroupAcknowledgment() ? 0 : 2 * docCount); ackStream.writeTrunk("SE" + delimiter + segmentCount + delimiter + CONTROL_NUMBER_997); ackStream.writeBranch("SE" + delimiter + "4" + delimiter + CONTROL_NUMBER_997); ackStream.write(terminatorWithSuffix); groupTrailerGenerated = true; } @Override public void generateNegativeACK() throws IOException { if (ackStream == null || skipFA || !headerGenerated) return; if (EDIReader.debug) EDIReader.trace("recasting 997 as negative"); if (!groupTrailerGenerated) generateGroupAcknowledgmentTrailer(0); generateAcknowledgementWrapup(false); } @Override public void generateAcknowledgementWrapup() throws IOException { generateAcknowledgementWrapup(true); } public void generateAcknowledgementWrapup(boolean positiveFA) throws IOException { if (ackStream == null || skipFA) return; if (EDIReader.debug) EDIReader.trace("generating GE, IEA"); // Generate the GE to match the GS ackStream.write("GE" + delimiter + thisDocumentCount + delimiter + thisGroupControlNumber); ackStream.write(terminatorWithSuffix); // Finish with an IEA corresponding to the ISA ackStream.write("IEA" + delimiter + "1" + delimiter + thisInterchangeControlNumber); ackStream.write(terminatorWithSuffix); if (positiveFA) ackStream.close(); else ackStream.closeUsingBranch(); } protected void generateAcknowledgementPreamble(String firstSegment, String groupSender, String groupReceiver, int groupDateLength, String groupVersion) throws IOException { if (ackStream == null || preambleGenerated) return; referencedISA = firstSegment; // Note that the initialization of the following items cannot occur // in the constructor because ansiReader may not have all of the // necessary information at that point. establishSyntaxCharacters(); // The ISA envelope is basically the same as that of the input // interchange except for reversal of the sender and receiver addresses. // Force the generated ISA to have fixed length fields, even if the input ISA does not. final String[] isaFields = splitOnDelimiter(); if (isaFields.length < 17) { throw new RuntimeException("*** Internal Error: Unable to interpret input ISA when forming ISA for acknowledgement. " + referencedISA); } String faHeader = isaFields[0] + delimiter + FixedLength.valueOf(isaFields[1], 2) + delimiter + FixedLength.valueOf(isaFields[2], 10) + delimiter + FixedLength.valueOf(isaFields[3], 2) + delimiter + FixedLength.valueOf(isaFields[4], 10) + delimiter + FixedLength.valueOf(isaFields[7], 2) + delimiter + FixedLength.valueOf(isaFields[8], 15) + delimiter + FixedLength.valueOf(isaFields[5], 2) + delimiter + FixedLength.valueOf(isaFields[6], 15) + delimiter + DateTimeGenerator.generate(delimiter) + delimiter + FixedLength.valueOf(isaFields[11], 1) + delimiter + FixedLength.valueOf(isaFields[12], 5) + delimiter + FixedLength.valueOf(isaFields[13], 9) + delimiter + FixedLength.valueOf(isaFields[14], 1) + delimiter + FixedLength.valueOf(isaFields[15], 1) + delimiter + FixedLength.valueOf(isaFields[16], 1); thisInterchangeControlNumber = isaFields[13].trim(); char senderDelimiter = standardReader.getDelimiter(); if (senderDelimiter != delimiter) faHeader = faHeader.replace(senderDelimiter, delimiter); ackStream.write(faHeader); ackStream.write(terminatorWithSuffix); if (standardReader.isInterchangeAcknowledgment()) { // TODO these need to avoid using fixed length assumptions ackStream.write("TA1" + delimiter + firstSegment.substring(90, 99) + delimiter + firstSegment.substring(70, 76) + delimiter + firstSegment.substring(77, 81) + delimiter); ackStream.writeTrunk("A" + delimiter + "000"); ackStream.writeBranch("R" + delimiter + "022"); ackStream.write(terminatorWithSuffix); } ackStream.write("GS" + delimiter + "FA" + delimiter + groupReceiver + delimiter + groupSender + delimiter + controlDateAndTime(groupDateLength, delimiter) + delimiter + thisGroupControlNumber + delimiter + "X" + delimiter + groupVersion); ackStream.write(terminatorWithSuffix); preambleGenerated = true; } private String[] splitOnDelimiter() { final String delimiterAsString = String.valueOf(referencedISA.charAt(3)); final String delimiterPattern = REGEX_CHARS_NEEDING_ESCAPE.contains(delimiterAsString) ? REGEX_ESCAPE + delimiter : delimiterAsString; return referencedISA.split(delimiterPattern); } private void establishSyntaxCharacters() { SyntaxDescriptor sd = standardReader.getAcknowledgmentSyntaxDescriptor(); char terminator = 0; String terminatorSuffix = null; if (sd == null) { delimiter = 0; } else { delimiter = sd.getDelimiter(); terminator = sd.getTerminator(); terminatorSuffix = sd.getTerminatorSuffix(); } if (delimiter == 0) delimiter = standardReader.getDelimiter(); if (terminator == 0) terminator = standardReader.getTerminator(); if (terminatorSuffix == null) terminatorSuffix = standardReader.getTerminatorSuffix(); terminatorWithSuffix = terminator + terminatorSuffix; } @Override public void generateAcknowledgmentHeader(String syntaxIdentifier, String syntaxVersionNumber, String fromId, String fromQual, String toId, String toQual, String interchangeControlNumber) { } }